ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

手把手系列 - 搭建 efk 8 收集 docker 容器日志

2022-07-07 12:01:03  阅读:187  来源: 互联网

标签:index filebeat 手把手 nginx efk elasticsearch 日志 docker


前言


查看网络上真正搭建使用 efk 8.0 版本的并不多,于是本文记录搭建过程。

本文书写时间:2022-07-018.2.2 搭建一套 docker 容器的日志收集系统。

elasticsearch: 8.2.2
filebeat: 8.2.2
kibana: 8.2.2

阅读本文前,请对 docker 日志有个正确的认识,请参考:

docker容器日志最佳实践


系统版本

System: CentOS Linux release 7.9.2009 (Core)
Kernel: 3.10.0-1160.el7.x86_64

要求:主机可访问互联网,本文采用 yum 安装。

注意:'>' 为命令输入提示符 '>' 后为输入的命令


docker 版本

Docker-CE
  * Server Version: 20.10.7
  * Storage Driver: overlay2

安装过程


主机名 ip地址
efk-node 192.168.1.101

安装顺序如下:

  1. 系统初始化
  2. docker-ce-20.10.7
  3. elasticsearch-8.2.2
  4. kibana-8.2.2
  5. filebeat-8.2.2

系统初始化

系统初始化分为以下几步:

  1. 修改主机名
  2. 关闭selinux 和 firewalld
  3. 配置国内yum源
  4. 校对时间

修改主机名

>hostnamectl set-hostname efk-node
>hostname efk-node
>echo "192.168.1.101  efk-node" >> /etc/hosts

#断开会话重新连接
root@efk-node(192.168.1.101)/root>hostname
efk-node

关闭selinux 和 firewalld

### 关闭 selinux
>sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
>systemctl disable firewalld
>reboot

配置国内yum源

>cd /etc/yum.repos.d/
#centos-7源
>curl http://mirrors.aliyun.com/repo/Centos-7.repo -o ./Centos-7.repo
>sed -i '/aliyuncs/d' Centos-7.repo

#docker-ce源
>curl http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -o ./docker-ce.repo

#epel-7源
>curl http://mirrors.aliyun.com/repo/epel-7.repo -o ./epel-7.repo

#efk源
>cat << EOF > elasticstack.repo
[elasticstack]
name = elasticstack
gpgcheck = 0
baseurl = https://mirrors.tuna.tsinghua.edu.cn/elasticstack/yum/elastic-8.x/
EOF

校对时间

>yum install -y ntpdate
>ntpdate tiger.sina.com.cn 

Docker-ce

  1. 安装docker-ce
>yum install -y docker-ce
  1. 添加docker-ce 配置
>mkdir /etc/docker/
>cat << 'EOF' > /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
	"max-file": "3"
  },
  "exec-opts": ["native.cgroupdriver=systemd"],
  "storage-driver": "overlay2",
  "storage-opts": [
    "overlay2.override_kernel_check=true"
  ],
  "registry-mirrors": [
    "https://docker.mirrors.ustc.edu.cn",
    "https://hub-mirror.c.163.com"
  ]
}
EOF
  1. 启动 docker
systemctl enable docker; systemctl start docker

查看docker 信息

>cat << 'EOF' >> /etc/sysctl.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
>sysctl --system

>docker info

Elasticsearch

Elasticsearch是个开源分布式搜索引擎,提供搜集、分析、存储数据三大功能。

  1. 安装elasticsearch
yum install -y elasticsearch-8.2.2

安装 elasticsearch 时候会出现如下提示:

image-20220706171507124

注意:这里用户名为:elastic ,密码可以修改,但是这里最好也记录一下。

用户名:elastic
密 码:j7CopSyDuOGhan*ZmI*u
  1. 修改默认配置文件
>cd /etc/elasticsearch/
# 修改操作前,一定要做好源文件的备份
>cp -a elasticsearch.yml elasticsearch.yml.orig
# 修改后的配置文件
>egrep -v "^#|^$" elasticsearch.yml
path.data: /var/lib/elasticsearch	//es数据存储目录
path.logs: /var/log/elasticsearch	//es日志存储目录
network.host: 192.168.1.101			//es监听地址
http.port: 9200						//es端口
### 这两项需要配置,否则启动会报错 ###
discovery.seed_hosts: ["efk-node"]	// 配置候选节点通信
### 开启x-pack 安全功能 及 https 功能
xpack.security.enabled: true
xpack.security.enrollment.enabled: true
xpack.security.http.ssl:	//启用https
  enabled: true
  keystore.path: certs/http.p12
xpack.security.transport.ssl:	//
  enabled: true
  verification_mode: certificate
  keystore.path: certs/transport.p12
  truststore.path: certs/transport.p12
cluster.initial_master_nodes: ["efk-node"]	// 集群初始化提供master地址
http.host: 0.0.0.0
### 解决跨越问题 ###
http.cors.enabled: true
http.cors.allow-origin: "*"
  1. 启动elasticsearch
systemctl enable elasticsearch; systemctl start elasticsearch

启动完成后,elasticsearch会监听 92009300

>netstat -ntplu | egrep java
tcp6       0      0 192.168.1.101:9200      :::*                    LISTEN      4305/java
tcp6       0      0 192.168.1.101:9300      :::*                    LISTEN      4305/java

elasticsearch-8.2.2 中,无法使用 http 访问, 需要使用: https://192.168.1.101:9200/

image-20220701173955193

image-20220706171703472

image-20220706171715445

Kibana

Kibana 也是一个开源和免费的工具,Kibana可以为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web 界面,可以帮助汇总、分析和搜索重要数据日志。

  1. 安装 kibana
>yum install -y kibana-8.2.2
  1. 修改 kibana 配置文件
>egrep -v "^#|^$" kibana.yml
server.port: 5601	# 端口
server.host: "0.0.0.0"	# 监听端口
elasticsearch.hosts: ["http://localhost:9200"]	# es链接
logging:
  appenders:
    file:
      type: file
      fileName: /var/log/kibana/kibana.log
      layout:
        type: json
  root:
    appenders:
      - default
      - file
pid.file: /run/kibana/kibana.pid
i18n.locale: "zh-CN"	# 汉化
  1. 启动 kibana
> systemctl enable kibana ; systemctl start kibana

kibana监听地址默认为:5601

>netstat -ntplu | egrep 5601
tcp        0      0 0.0.0.0:5601            0.0.0.0:*               LISTEN      5784/node
  1. 通过浏览器访问

image-20220706172840150

在centos7上获取命令如下:

> /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s kibana

执行以后,将 token 复制到【注册令牌】中。

image-20220706173056210

点击 【配置 Elastic】 后,会跳出验证码:

image-20220706173150240

查看 kibana 验证码:

image-20220706173214304

输入验证码后,等待 kibana 验证完毕

image-20220706173240388

完成后,出现登录界面

image-20220706173258323

用户名密码就是安装 elasticsearch 生成的用户名密码:

用户名:elastic
密 码:j7CopSyDuOGhan*ZmI*u

image-20220706173358537

Filebeat

  1. 安装 filebeat
>yum install -y filebeat-8.2.2

到此,所有安装的软件包都已经安装就位,接下来就是配置的修改及调整。所有软件包如下:

> rpm -qa | egrep "docker-ce|elasticsearch|kibana|filebeat"
elasticsearch-8.2.2-1.x86_64
filebeat-8.2.2-1.x86_64
docker-ce-20.10.7-3.el7.x86_64
kibana-8.2.2-1.x86_64
docker-ce-cli-20.10.7-3.el7.x86_64
docker-ce-rootless-extras-20.10.7-3.el7.x86_64

配置调试过程

本次以收集 nginx 容器日志为例,进行调试配置。

  1. 编写收集容器日志的 filebeat 配置文件
### 一定要在 /etc/filebeat/ 目录下编写配置文件,否则无法使用调试模式启动。
>cd /etc/filebeat/

在编写 filebeat 配置文件之前,得了解下 filebeat 配置文件结构,强烈建议参考官方文档:

https://www.elastic.co/guide/en/beats/filebeat/8.2/directory-layout.html

image-20220706173936963

通过目录,可以简单理解,filebeat 分为 inputs 和 output 两部分,首先查看 inputs

image-20220706174045336

对比 7.14 版本的 filebeat,在8.2.2中已经弃用了 docker 模块,只能使用 container

image-20220706174555586

这里有直接的配置示例,直接抄写即可。

接下来,查看 output 部分。

image-20220706174723049

因为,我们处于配置调试节点,所以将日志收集直接打印在控制台展示,如果觉得数据没问题,再进行配置到 elasticsearch 中,所以这里直接查看 Console

image-20220706174636580

直接抄写 output 配置

综上,目前的 filebeat 配置文件如下:

>cat /etc/filebeat/docker-nginx.yml
filebeat.inputs:
- type: container
  paths:
    - '/var/lib/docker/containers/*/*.log'

output.console:
  pretty: true

通过 filebeat 调试模式启动。

/etc/filebeat> filebeat -e -c docker-nginx.yml

既然要收集 nginx 容器的日志,就得有 nginx 容器,这里启动 nginx 容器

>docker run --name ngx -p 80:80 -d nginx:alpine

当容器启动成功后,filebeat 控制台就会打印如下信息日志:

{
  "@timestamp": "2022-07-06T09:54:34.861Z",
  "@metadata": {
    "beat": "filebeat",
    "type": "_doc",
    "version": "8.2.2"
  },
  "host": {
    "name": "efk-node"
  },
  "agent": {
    "ephemeral_id": "21665b96-2ac4-4213-ae68-5be81c505ef9",
    "id": "90fe1004-6c5c-4755-9372-a154c57bf136",
    "name": "efk-node",
    "type": "filebeat",
    "version": "8.2.2"
  },
  "ecs": {
    "version": "8.0.0"
  },
  "log": {
    "offset": 2444,
    "file": {
      "path": "/var/lib/docker/containers/4c4e1811670c5745ad1307bf21ddfec7e63dd140388a012c07729da2308c2375/4c4e1811670c5745ad1307bf21ddfec7e63dd140388a012c07729da2308c2375-json.log"
    }
  },
  "stream": "stderr",
  "message": "2022/07/06 09:54:34 [notice] 1#1: start worker process 36",
  "input": {
    "type": "container"
  }
}

出现这样的信息,说明filebeat 已经捕获到了 容器的日志信息

接下来就要对日志信息进行筛选,大段的日志信息有很多我们并不需要过多的去关注,因此需要剔除无用的信息,继续查看官方文档:

image-20220706180738051

在上面除了,Inputoutput 还有一个 Processors 处理器,这个 Processors 也可以理解为 filter ,筛选器

image-20220706180802085

Processors 子类中,找到了 drop_fields 删除字段配置,打开查看。

image-20220706180820901

通过官方示例查看,when 当...时候... 这里就是通过条件筛选进行字段的删除,目前我们这里没有条件筛选,尝试直接删除字段。

注意:通过官方文档查看 processors 是与 input 和 output 平级的,所以注意格式。

于是,配置文件又修改为如下:

# /etc/filebeat/docker-nginx.yml
filebeat.inputs:
- type: container
  paths:
    - '/var/lib/docker/containers/*/*.log'

processors:
- drop_fields:
    fields: ["log","agent","ecs"]

output.console:
  pretty: true

在刚才的控制台 ctrl + c 结束,重新启动

>filebeat -e -c docker-nginx.yml

浏览器访问 nginx 后,查看控制台:

{
  "@timestamp": "2022-07-06T10:05:49.917Z",
  "@metadata": {
    "beat": "filebeat",
    "type": "_doc",
    "version": "8.2.2"
  },
  "stream": "stdout",
  "message": "192.168.1.2 - - [06/Jul/2022:10:05:49 +0000] \"GET / HTTP/1.1\" 304 0 \"-\" \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36\" \"-\"",
  "input": {
    "type": "container"
  },
  "host": {
    "name": "efk-node"
  }
}

到目前为止,已经提取出来对我们有用的日志信息,接下来就可以存储到 elasticsearch 中,查看官方文档如何存储到 es 中。

image-20220706181839108

在 8 的版本中, elasticsearch 采用https 并开启了用户认证,这是区别 7 版本唯一的不同。官方文档也是采用三种不同的形式进行认证。

通过查证,可使用如下方式写入 elasticsearch :

output.elasticsearch:
  hosts: ["https://[ES地址]:9200"]
  ssl.verification_mode: "none"
  username: "elastic"
  password: "[密码]"

结合之前 filebeat 配置文件:

### /etc/filebeat/docker-nginx.yml
filebeat.inputs:
- type: container
  paths:
    - '/var/lib/docker/containers/*/*.log'

processors:
- drop_fields:
    fields: ["log","agent","ecs"]

#output.console:
#  pretty: true

output.elasticsearch:
  hosts: ["https://192.168.1.101:9200"]
  ssl.verification_mode: "none"
  username: "elastic"
  password: "j7CopSyDuOGhan*ZmI*u"
  
### 加入以下内容,output.elasticsearch.index 才会生效
setup.ilm.enabled: false            # 关闭索引生命周期
setup.template.enabled: false       # 允许自动生成index模板
setup.template.overwrite: true      # 如果存在模块则覆盖
#setup.template.name: "docker"       # 生成index模板的名称
#setup.template.pattern: "docker-*"  # 生成index模板匹配的index格式

重启控制台

ctrl+c
>filebeat -e -c docker-nginx.yml

浏览器访问 nginx 容器生成日志,然后浏览器查看 es 中所有索引信息

image-20220707112620936

我们通过 filebeat 存储进去的日志信息。这个索引名怎么看怎么奇怪,因为存储的是nginx 容器的日志,所以做好将索引名修改为 nginx 相关的名字,这样就可以通过索引名确定存储的数据信息。

image-20220706182606523

修改 配置文件如下:

### /etc/filebeat/docker-nginx.yml
filebeat.inputs:
- type: container
  paths:
    - '/var/lib/docker/containers/*/*.log'

processors:

- drop_fields:
    fields: ["log","agent","ecs"]

# output.console:
#   pretty: true
output.elasticsearch:
  hosts: ["https://192.168.1.101:9200"]
  ssl.verification_mode: "none"
  username: "elastic"
  password: "j7CopSyDuOGhan*ZmI*u"
  index: "docker-nginx-%{+yyyy.MM.dd}"
  
### 加入以下内容,output.elasticsearch.index 才会生效
setup.ilm.enabled: false            # 关闭索引生命周期
setup.template.enabled: false       # 允许自动生成index模板
setup.template.overwrite: true      # 如果存在模块则覆盖
#setup.template.name: "docker"       # 生成index模板的名称
#setup.template.pattern: "docker-*"  # 生成index模板匹配的index格式

重启控制台

>filebeat -e -c docker-nginx.yml

浏览器访问后,查看索引

image-20220707113002397

这里生成了 docker-nginx-* 索引

接下来就可以通过 kibana 展示出来。

image-20220707113150441

image-20220707113217283

image-20220707113238176

image-20220707113327848

这样就将 nginx 容器的日志信息收集展示出来了。

进阶配置过程

现在的数据就简化了很多,只保留对我们有用的信息。接下来就要考虑几个问题

  1. nginx 日志分为 access.log 和 error.log 如何区分存储?

  2. 如果多个容器如果区分不同的容器日志?

stdout 及 stderr 存入不同索引

对于 nginx来说,可能只需要关注 错误日志,如何区分存储呢?其实查看上面控制台输出的日志内容不难发现一个字段:stream

{
  "@timestamp": "2022-06-30T07:42:33.163Z",
  "@metadata": {
    "beat": "filebeat",
    "type": "_doc",
    "version": "7.17.5"
  },
  "input": {
    "type": "container"
  },
  "host": {
    "name": "efk-node"
  },
  "stream": "stdout",  //这个字段
  "message": "..."
}

其实对于 nginx 这样的容器,遵循一个标准,标准输出到 stdout 标准错误输出到 stderr 可进入容器查看:

~ # ls -l /var/log/nginx/
total 0
lrwxrwxrwx    1 root     root            11 Jun 22 19:20 access.log -> /dev/stdout
lrwxrwxrwx    1 root     root            11 Jun 22 19:20 error.log -> /dev/stderr

所以,对于 stream: stderr 就是需要关注的 错误日志,所以根据字段条件进行区分,修改 filebeat 配置文件如下:

### /etc/filebeat/docker-nginx.yml
filebeat.inputs:
- type: container
  paths:
    - '/var/lib/docker/containers/*/*.log'

processors:

- drop_fields:
    fields: ["log","agent","ecs"]

# output.console:
#   pretty: true
output.elasticsearch:
  hosts: ["https://192.168.1.101:9200"]
  ssl.verification_mode: "none"
  username: "elastic"
  password: "j7CopSyDuOGhan*ZmI*u"
  indices:
  - index: "docker-nginx-access-%{+yyyy.MM.dd}"
    when.contains:
      stream: "stdout"
  - index: "docker-nginx-error-%{+yyyy.MM.dd}"
    when.contains:
      stream: "stderr"
  
### 加入以下内容,output.elasticsearch.index 才会生效
setup.ilm.enabled: false            # 关闭索引生命周期
setup.template.enabled: false       # 允许自动生成index模板
setup.template.overwrite: true      # 如果存在模块则覆盖
#setup.template.name: "docker"       # 生成index模板的名称
#setup.template.pattern: "docker-*"  # 生成index模板匹配的index格式

image-20220707113736039

根据了不同的条件存入不不同的索引中,这样可以通过 kibana 来展示出来。

image-20220707113914434

这样, 就可以通过不同的索引展示各种所需日志数据。

总结

该部分解决的问题是:单容器,正常日志和错误日志分类存放的问题。

多容器及不同业务之间的日志汇集需要看下一个部分。

不同业务容器存入不同索引

容器使用场景,肯定会存在不同的程序或者不同的业务都运行于容器当中。举个最简单的架构 lnmp ,nginx 和 php 的日志如果存储在同一个索引里,日后查询排错还不如直接查看源日志文件,这就违背了搭建日志收集系统的初衷。

通过上面 filebeat 中 inputs 可以看到是 通过解析容器日志目录来获取日志信息的,而容器的名称和ID 都会随着生命周期而变动的,因此无法像物理或者虚拟主机一样通过IP联系起来。这个时候就需要为容器打标签,通过打标签的形式区分不同业务的容器集合。

注意:做这一部分示例前,请将运行中的容器关闭并删除、关闭 filebeat

docker rm -f `docker ps -aq`
systemctl stop filebeat

为容器打标签

  1. docker 直接启动打标签
docker run --name nginx -p 80:80 --label service=nginx --log-opt labels=service -d nginx:alpine
  1. 通过docker-compose 打标签
### docker-compose.yml
version: "3"
services:
  nginx:
    container_name: "nginx"
    image: nginx:alpine
    environment:
    - "TZ=Asia/Shanghai"
    labels:
      service: nginx
    logging:
      options:
        labels: "service"
    ports:
      - "80:80"

上面两种方式,任意执行一种。

通过浏览器访问 http://192.168.1.101/ , 查看容器日志:

>tail -1 /var/lib/docker/containers/b066871ed799d21c3633341ec15dcfdc712067b483b7a4c7e3b3f909cf380940/b066871ed799d21c3633341ec15dcfdc712067b483b7a4c7e3b3f909cf380940-json.log | jq

{
  "log": "192.168.1.2 - - [01/Jul/2022:01:51:17 +0000] \"GET / HTTP/1.1\" 304 0 \"-\" \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36\" \"-\"\n",
  "stream": "stdout",
  "attrs": {
    "service": "nginx"
  },
  "time": "2022-07-01T01:51:17.717295543Z"
}

日志中多有 attrs 属性字段,就是打的标签,然后可通过 filebeat output 中 when 条件来过滤。

示例:

需求:有两个不同的 web 服务, 一个开启80端口,一个开启 8080 端口,需要对它们日志存储到不同的索引里。

这里使用 docker-compose 启动容器,docker-compose如下:

### /root/manifests/docker-compose.yml
version: "3"
services:
  # 80 web 服务
  nginx:
    container_name: "nginx"
    image: nginx:alpine
    environment:
    - "TZ=Asia/Shanghai"
    labels:
      service: nginx		# 标记
    logging:
      options:
        labels: "service"
    ports:
      - "80:80"
  # 8080 web 服务
  httpd:
    container_name: "httpd"
    image: httpd:latest
    environment:
    - "TZ=Asia/Shanghai"
    labels:
      service: httpd		# 标记
    logging:
      options:
        labels: "service"
    ports:
      - "8080:80"

使用 dokcer-compose 启动

>docker-compose up -d
>docker-compose ps
NAME                COMMAND                  SERVICE             STATUS              PORTS
httpd               "httpd-foreground"       httpd               running             0.0.0.0:8080->80/tcp, :::8080->80/tcp
nginx               "/docker-entrypoint.…"   nginx               running             0.0.0.0:80->80/tcp, :::80->80/tcp

然后编写 filebeat 配置文件:

#文件目录: /etc/filebeat/docker-compose-web.yml
filebeat.inputs:
- type: container
  enabled: true
  paths:
  - '/var/lib/docker/containers/*/*.log'


processors:
- drop_fields:
   fields: ["log","ecs","agent"]


output.elasticsearch:
  hosts: ["https://192.168.1.101:9200"]
  ssl.verification_mode: "none"
  username: "elastic"
  password: "j7CopSyDuOGhan*ZmI*u"
  indices:
    - index: "web-nginx-access-%{+yyyy.MM.dd}"
      when.contains:
        # 日志中包括 docker.attrs.service==nginx 且 stream=stdout 存入web-nginx-access-%{+yyyy.MM.dd}
        docker.attrs.service: "nginx"   
        stream: "stdout"
    - index: "web-nginx-error-%{+yyyy.MM.dd}"
      when.contains:
        # 日志中包括 docker.attrs.service==nginx 且 stream=stderr 存入web-nginx-error-%{+yyyy.MM.dd}
        docker.attrs.service: "nginx"
        stream: "stderr"

    - index: "web-httpd-access-%{+yyyy.MM.dd}"
      when.contains:
        # 日志中包括 docker.attrs.service==httpd 且 stream=stdout 存入web-httpd-access-%{+yyyy.MM.dd}
        docker.attrs.service: "httpd"
        stream: "stdout"

    - index: "web-httpd-error-%{+yyyy.MM.dd}"
      when.contains:
        # 日志中包括 docker.attrs.service==httpd 且 stream=stderr 存入web-httpd-error-%{+yyyy.MM.dd}
        docker.attrs.service: "httpd"
        stream: "stderr"

### 加入以下内容,output.elasticsearch.index 才会生效
setup.ilm.enabled: false            # 关闭索引生命周期
setup.template.enabled: false       # 允许自动生成index模板
setup.template.overwrite: true      # 如果存在模块则覆盖
#setup.template.name: "docker"       # 生成index模板的名称
#setup.template.pattern: "docker-*"  # 生成index模板匹配的index格式

启动:

>cd /etc/filebeat
>filebeat -e -c docker-compose-web.yml

通过浏览器访问后,查看 索引

image-20220707114654517

这里就通过不同的条件将不同业务不同类型的日志进行了分开存储,然后在 kibana 如上配置,就可以分类查看。

image-20220707114927760

标签:index,filebeat,手把手,nginx,efk,elasticsearch,日志,docker
来源: https://www.cnblogs.com/hukey/p/16454187.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有