Docker 本地registry创建及image上传

通常情况下我们可以使用https://hub.docker.com/作为docker image的仓库,但是有些场景下,我们希望能够有本地的仓库。比如:

  • 代码中含有保密的信息,比如环境的账号,密码等等;
  • 代码本身作为公司的资产,不能对外公开,否则有法律风险。

在创建本地仓库之前,请确保已经在目的机器上安装了Docker。这里我们使用docker容器运行registry镜像的方式,来创建registry。

一般情况下安装的docker已经自带了registry镜像,如果没有可以从docker hub上获取。

在docker容器中启动registry镜像

docker@default:~$ run -d -p 5000:5000 localregistry registry

其中 localregistry表示此容器的名称,registry表示了镜像本身。可以运行docker ps查看结果:

docker@default:~$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
afae402eb9ae        registry            "/entrypoint.sh /e..."   4 hours ago         Up 20 minutes       0.0.0.0:5000->5000/tcp   localregistry

自动启动仓库

如果想让registry作为永久的可用仓库,应该在Docker machine重启或退出之后,设置registry仍然能够自动重启或保持使用状态。可以使用–restart=always达到此目的。

docker@default:~$ docker run -d -p 5000:5000 --restart=always --name localregistry registry

当Docker从registry获取image,或上传image到registry时,它会判断pull或push的uri的第一部分是否包含‘.’或‘:’来判断,是仓库名称还是用户名。

以localhost为例,如果url中只包含localhost,而没有‘.’或 :5000, Docker会认为localhost是用户名,效果就如同localhost/ubuntu或wxqsly/snapshot一样,此时Docker会请求默认的Docker Hub仓库。否则Docker会认为localhost代表了主机名,并且会请求到你指定的仓库。

从Docker Hub获取ubuntu镜像

docker@default:~$ docker pull ubuntu:16.04

将镜像标记为localhost:5000/my-ubuntu,这实际上给已有的镜像增加了额外的标记。当uri的第一部分为主机名(加端口),Docker在push的时候会认为第一部分为仓库的地址。如下例子将标记的镜像push到本地的仓库。

$ docker tag ubuntu:16.04 localhost:5000/my-ubuntu
$ docker push localhost:5000/my-ubuntu

docker基础命令(备忘)

查看本地镜像

docker images

删除本地镜像

docker images # 拿到IMAGE ID
docker rmi da5939581ac8
# rmi => remove image

查看运行过的镜像

docker ps // 查看正在运行的镜像
docker ps -a // 查看所以运行过的镜像(包括正在运行和已经停止运行的)

清除运行过的镜像记录

docker ps -a # 拿到容器的ID
docker rm 57611fd7da07 585f867a6858
# 后面可以跟上多个id,也就可以删除多个

下载docker镜像到本地

docker pull nginx

# nginx 就是docker官方镜像网站上的一个nginx镜像

启动镜像

docker run nginx

停止容器

docker ps // 拿到容器的ID
docker stop 0b234366c3d2

暴露端口

docker run -p 8080:80 nginx

# 将nginx镜像里的80端口暴露出来,暴露出来的端口为8080,也就是说,可以直接用浏览器访问 http://localhost:8080 就可以了,可以看到一个经典的nginx的默认页面

后台运行

docker run -d nginx
# d => daemon

拷贝文件到docker容器里

docker ps # 拿到正在运行容器的ID

# 新建一个index.html文件,里面写上 `hello world`
docker cp index.html 0b234366c3d2://usr/share/nginx/html

# 两次访问 http://localhost:8080 页面就变成了hello world

这时候如果把这个正在运行的docker容器停掉的话,下次再启动的时候,就又是默认的页面了,之前拷贝进去的index.html没有了,怎么保存呢?看下面

保存镜像

docker ps # 拿到正在运行容器的ID
docker commit -m 'message' 0b234366c3d2 my-nginx

# -m 是保存镜像的描述信息
# 0b234366c3d2 是容器的ID
# my-nginx 是保存后的镜像名字

挂载文件

上面说到,如果容器被停止了,那复制到容器里的文件也都没有了,可以用 docker commit 的方式保存下来,是个办法,下面说一下挂载的方法,更简单实用

# 在当前目录新建一个文件夹 `html`
# 把上面新建的那个 `index.html` 文件拷贝到html里
docker run -d -p 8080:80 -v $PWD/html:/usr/share/nginx/html nginx
# -v 就是挂载参数
# $PWD shell命令,输出当前目录的绝对地址

# 这样挂载后,在本地的html文件夹里修改index.html,然后浏览器访问是实时更新的,停掉了容器也不用担心文件会消失

进入容器

docker ps # 拿到正在运行容器的ID
docker exec -it 0b234366c3d2 bash

docker overlay网络实现

DOCKER的内置OVERLAY网络

内置跨主机的网络通信一直是Docker备受期待的功能,在1.9版本之前,社区中就已经有许多第三方的工具或方法尝试解决这个问题,例如Macvlan、Pipework、Flannel、Weave等。

虽然这些方案在实现细节上存在很多差异,但其思路无非分为两种: 二层VLAN网络和Overlay网络

简单来说,二层VLAN网络解决跨主机通信的思路是把原先的网络架构改造为互通的大二层网络,通过特定网络设备直接路由,实现容器点到点的之间通信。这种方案在传输效率上比Overlay网络占优,然而它也存在一些固有的问题。

这种方法需要二层网络设备支持,通用性和灵活性不如后者。

由于通常交换机可用的VLAN数量都在4000个左右,这会对容器集群规模造成限制,远远不能满足公有云或大型私有云的部署需求; 大型数据中心部署VLAN,会导致任何一个VLAN的广播数据会在整个数据中心内泛滥,大量消耗网络带宽,带来维护的困难。

相比之下,Overlay网络是指在不改变现有网络基础设施的前提下,通过某种约定通信协议,把二层报文封装在IP报文之上的新的数据格式。这样不但能够充分利用成熟的IP路由协议进程数据分发;而且在Overlay技术中采用扩展的隔离标识位数,能够突破VLAN的4000数量限制支持高达16M的用户,并在必要时可将广播流量转化为组播流量,避免广播数据泛滥。

因此,Overlay网络实际上是目前最主流的容器跨节点数据传输和路由方案。

容器在两个跨主机进行通信的时候,是使用overlay network这个网络模式进行通信;如果使用host也可以实现跨主机进行通信,直接使用这个物理的ip地址就可以进行通信。overlay它会虚拟出一个网络比如10.0.2.3这个ip地址。在这个overlay网络模式里面,有一个类似于服务网关的地址,然后把这个包转发到物理服务器这个地址,最终通过路由和交换,到达另一个服务器的ip地址。

未分类

要实现overlay网络,我们会有一个服务发现。比如说consul,会定义一个ip地址池,比如10.0.2.0/24之类的。上面会有容器,容器的ip地址会从上面去获取。获取完了后,会通过ens33来进行通信,这样就实现跨主机的通信。

未分类

环境说明

未分类

[root@client1 ~]# cat /etc/redhat-release 
CentOS Linux release 7.3.1611 (Core) 
[root@client2 ~]# cat /etc/redhat-release 
CentOS Linux release 7.3.1611 (Core) 
[root@client1 ~]# docker -v 
Docker version 1.12.6, build 78d1802
[root@client2 ~]# docker -v 
Docker version 1.12.6, build 78d1802

修改docker配置

修改它的启动参数,这里的ip等要修改成自己的。

[root@client1 ~]# cat /lib/systemd/system/docker.service | grep "ExecStart=/usr/bin/dockerd"
ExecStart=/usr/bin/dockerd  -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock --cluster-store=consul://192.168.6.134:8500 --cluster-advertise=ens33:2376 --insecure-registry=0.0.0.0/0

[root@client2~]# cat /lib/systemd/system/docker.service | grep "ExecStart=/usr/bin/dockerd"
ExecStart=/usr/bin/dockerd  -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock --cluster-store=consul://192.168.6.135:8500 --cluster-advertise=ens33:2376 --insecure-registry=0.0.0.0/0

修改完后,需要重启。

[root@client1 ~]# systemctl daemon-reload
[root@client1 ~]# systemctl restart docker

[root@client2 ~]# systemctl daemon-reload
[root@client2 ~]# systemctl restart docker

查看重启后是否启动成功。

[root@client1 ~]# ps aux | grep dockerd
root     107931  0.4  2.7 507600 27908 ?        Ssl  16:52   0:00 /usr/bin/dockerd -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock --cluster-store=consul://192.168.6.134:8500 --cluster-advertise=ens33:2376 --insecure-registry=0.0.0.0/0

[root@client1 ~]# ps aux | grep dockerd
root     107931  0.4  2.7 507600 27908 ?        Ssl  16:52   0:00 /usr/bin/dockerd -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock --cluster-store=consul://192.168.6.135:8500 --cluster-advertise=ens33:2376 --insecure-registry=0.0.0.0/0

在第一台主机上创建一个consul。

[root@client1 ~]# docker run -d -p 8400:8400 -p 8500:8500 -p 8600:53/udp -h consul progrium/consul -server -bootstrap -ui-dir /ui

查看启动是否成功。

[root@client1 ~]# docker ps 
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                                                                                        NAMES
561ad5f39ca5        progrium/consul     "/bin/start -server -"   27 hours ago        Up 7 seconds        53/tcp, 0.0.0.0:8400->8400/tcp, 8300-8302/tcp, 8301-8302/udp, 0.0.0.0:8500->8500/tcp, 0.0.0.0:8600->53/udp   focused_spence

创建完后通过浏览器访问一下,可以看到这两台会自动注册上来,这样的话这两个主机之间就会进行通信。

未分类

在第一台主机上创建一个overlay网络。

[root@client1 ~]# docker network create -d overlay --subnet=10.0.2.1/24 overlay-net 
80e398c37493ec1a4132efa56572a9212ac5688b557772d295c21a0d0916120b
[root@client1 ~]# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
9008047151be        bridge              bridge              local                             
bd6513b8baec        host                host                local               
6aed7659391a        none                null                local               
80e398c37493        overlay-net         overlay             global  

这边自动回进行通步,因为使用的是同一个服务器发件。

[root@client2 ~]# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
9008047171be        bridge              bridge              local                             
bd6513b8baec        host                host                local               
6aed7449391a        none                null                local               
80e399c37493        overlay-net         overlay             global

创建一个使用overlay网络的容器。

[root@client1 ~]# docker run -d --name app1 --net=overlay-net registry
c8ec2b34c97abd2e563d236d6fe1b51686f7a9440f6eb171392ca0a6221c2b7a

查看是否创建成功。

root@client1 ~]# docker ps 
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                                                                                        NAMES
c8ec2b34c97a        registry            "/entrypoint.sh /etc/"   About an hour ago   Up 6 seconds        5000/tcp                                                                                                     app1

登陆进去查看ip地址是否是10.0.2.0的网段。

[root@client1 ~]# docker exec -it app1 sh
/ # ifconfig 
eth0      Link encap:Ethernet  HWaddr 02:42:0A:00:02:02  
     inet addr:10.0.2.2  Bcast:0.0.0.0  Mask:255.255.255.0
     inet6 addr: fe80::42:aff:fe00:202%32683/64 Scope:Link
     UP BROADCAST RUNNING MULTICAST  MTU:1450  Metric:1
     RX packets:15 errors:0 dropped:0 overruns:0 frame:0
     TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
     collisions:0 txqueuelen:0 
     RX bytes:1206 (1.1 KiB)  TX bytes:648 (648.0 B)

eth1      Link encap:Ethernet  HWaddr 02:42:AC:13:00:02  
     inet addr:172.19.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
     inet6 addr: fe80::42:acff:fe13:2%32683/64 Scope:Link
     UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
     RX packets:14 errors:0 dropped:0 overruns:0 frame:0
     TX packets:14 errors:0 dropped:0 overruns:0 carrier:0
     collisions:0 txqueuelen:0 
     RX bytes:1217 (1.1 KiB)  TX bytes:1074 (1.0 KiB)

lo        Link encap:Local Loopback  
     inet addr:127.0.0.1  Mask:255.0.0.0
     inet6 addr: ::1%32683/128 Scope:Host
     UP LOOPBACK RUNNING  MTU:65536  Metric:1
     RX packets:4 errors:0 dropped:0 overruns:0 frame:0
     TX packets:4 errors:0 dropped:0 overruns:0 carrier:0
     collisions:0 txqueuelen:1 
     RX bytes:379 (379.0 B)  TX bytes:379 (379.0 B)

它也具备一个nat网络模式。

/ # ping www.baidu.com
PING www.baidu.com (61.135.169.125): 56 data bytes
64 bytes from 61.135.169.125: seq=0 ttl=127 time=27.659 ms
64 bytes from 61.135.169.125: seq=1 ttl=127 time=29.027 ms
^C
--- www.baidu.com ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 27.659/28.343/29.027 ms

在另一台机器上面同样创建一个overlay网路的容器。

[root@client2 ~]# docker run -d --name app2 --net=overlay-net registry
[root@client2 ~]# docker exec -it app2 sh

查看ip地址。

UP BROADCAST RUNNING MULTICAST  MTU:1450  Metric:1
     RX packets:13 errors:0 dropped:0 overruns:0 frame:0
     TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
     collisions:0 txqueuelen:0 
     RX bytes:1038 (1.0 KiB)  TX bytes:648 (648.0 B)

eth1      Link encap:Ethernet  HWaddr 02:42:AC:14:00:02  
     inet addr:172.20.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
     inet6 addr: fe80::42:acff:fe14:2%32667/64 Scope:Link
     UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
     RX packets:14 errors:0 dropped:0 overruns:0 frame:0
     TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
     collisions:0 txqueuelen:0 
     RX bytes:1080 (1.0 KiB)  TX bytes:648 (648.0 B)

lo        Link encap:Local Loopback  
     inet addr:127.0.0.1  Mask:255.0.0.0
     inet6 addr: ::1%32667/128 Scope:Host
     UP LOOPBACK RUNNING  MTU:65536  Metric:1
     RX packets:0 errors:0 dropped:0 overruns:0 frame:0
     TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
     collisions:0 txqueuelen:1 
     RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

查看client1和client2的容器网络是否可以互通。

/ # ping 10.0.2.2
PING 10.0.2.2 (10.0.2.2): 56 data bytes
64 bytes from 10.0.2.2: seq=0 ttl=64 time=0.748 ms
64 bytes from 10.0.2.2: seq=1 ttl=64 time=0.428 ms
64 bytes from 10.0.2.2: seq=2 ttl=64 time=1.073 ms
^C
--- 10.0.2.2 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.428/0.749/1.073 ms

/ # ping app1
PING app1 (10.0.2.2): 56 data bytes
64 bytes from 10.0.2.2: seq=0 ttl=64 time=0.418 ms
64 bytes from 10.0.2.2: seq=1 ttl=64 time=0.486 ms
64 bytes from 10.0.2.2: seq=2 ttl=64 time=0.364 ms
^C
--- app1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.364/0.422/0.486 ms

到此, 在client2主机上的容器可以直接互通。我们这里实现了跨主机通信,是通过overlay network这种网络模式进行通信的。

最小化安装 CentOS 7.3 部署 Cacti-1.1.23 详细步骤

Cacti 是一个利用 RRDTool 数据存储和绘图功能的完整的网络图形解决方案。Cacti 提供了快速的数据轮询、高级图形模板、多样的数据获取方法以及用户管理等功能。并以直观易于使用的接口来呈现,这在具有成千上万个设备的复杂网络上是有意义的。

一、安装环境

安装 Cacti 需要 Mysql,PHP,RRDTool,net-snmp 和 支持 PHP 的 Web服务器,如 Apache,Nginx 或 IIS等。

版本要求:

  • RRDTool 1.3 或更高版本,http://oss.oetiker.ch/rrdtool/

  • MySQL 5.1 或更高版本,https://mysql.com/

  • MariaDB 5.5 或更高版本,https://mariadb.org/

  • PHP 5.4 或更高版本,https://php.net/

  • 支持PHP的Web服务器,如Apache,NGINX 或 IIS(https://www.apache.org/,https://www.nginx.com/)

本次安装 Cacti-1.1.23 使用相关软件及版本为:

  • RRDTool 1.4.8

  • MariaDB 10.2.8 (mariadb mariadb-server)

  • PHP 5.4.16 (php php-snmp php-ldap php-xml php-session php-sockets php-pdo php-pdo-mysql php-mbstring php-pcre php-json php-openssl php-gd php-gmp php-zlib)

  • Apache httpd 2.4.6

  • net-snmp 5.7.2 (net-snmp net-snmp-utils)

  • crond (cronie 或其他类似软件)

为了安装官方补丁,还需要安装一些工具软件

  • wget

  • patch

二、软件安装

2.1 更新 YUM 源

添加 MariaDB YUM 仓库

# cat > /etc/yum.repos.d/MariaDB.repo <<EOF

[mariadb]

name = MariaDB

baseurl = http://yum.mariadb.org/10.2/centos7-amd64

gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB

gpgcheck=1

EOF

2.2 安装 Cacti 部署环境所需软件包

# yum install -y httpd mariadb mariadb-server mariadb-devel net-snmp net-snmp-utils net-snmp-libs net-snmp-agent-libs net-snmp-devel php php-snmp php-ldap php-pdo php-mysql php-devel php-pear php-common php-gd php-mbstring php-xml php-process rrdtool rrdtool-php rrdtool-perl rrdtool-devel gcc openssl-devel dos2unix autoconf automake binutils libtool cpp postfix glibc-headers kernel-headers glibc-devel gd gd-devel help2man ntpdate wget patch

三、环境配置

3.1 配置 PHP

修改 PHP 主配置文件 /etc/php.ini

关闭安全模式,以支持 rrdtool,允许 exec() 访问

[PHP]

safe_mode = Off

设置时区

[Date]

date.timezone = PRC

3.2 配置 Web 服务器 Apache httpd

编辑 /etc/httpd/conf.d/php.conf 配置文件,确保文件含有

# PHP is an HTML-embedded scripting language which attempts to make it

# easy for developers to write dynamically generated webpages.

LoadModule php5_module modules/libphp5.so

#

# Cause the PHP interpreter to handle files with a .php extension.

AddHandler php5-script .php

如果你不打算将 Cacti 安装到 Web 服务器默认目录,如/var/www/html,就需要将下面内容添加

至 /etc/httpd/conf.d/cacti.conf 文件,并将其中的 /your/cacti/dir 替换为实际安装目录,将

Allow from 127.0.0.1 改为你公司所在网段。

# Cacti - the complete rrdtool-based graphing solution

# 

# Allows only localhost by default

#

# Allowing cacti to anyone other than localhost should be considered

# dangerous unless properly secured by SSL

# Make sure, that httpd can read your cacti directories.

# At minimum, you need

#    chmod ugo+r -R /your/cacti/dir

# Make sure to replace with your directories

# When using SELinux, set the following:

#    chcon -R -h -t httpd_sys_content_t /your/cacti/dir

# when using SELinux and you private homedir, enable

#    setsebool -P httpd_enable_homedirs 1

#    setsebool -P httpd_read_user_content 1

Alias /cacti /var/www/html/cacti

<Directory /var/www/html/cacti>

   AllowOverride None

   Order Deny,Allow

   Deny from all

   Allow from 127.0.0.1

   Allow from ::1

   Options Indexes Includes FollowSymLinks

</Directory>

# These directories do not require access over HTTP 

#

<Directory /var/www/html/cacti/cli>

    Order Deny,Allow

    Deny from All

    Allow from None

</Directory>

3.3 配置数据库 MariaDB

为安全起见,设置数据库 root 用户密码

# systemctl start mariadb

# mysqladmin -uroot password somepassword

导入时区数据到 mysql 数据库

# mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -uroot mysql -p

修改 /etc/my.cnf.d/server.cnf 文件,确保文件含有

[mysqld]

character_set_server = utf8mb4

collation_server = utf8mb4_unicode_ci

character_set_client = utf8mb4

max_connections = 100

max_heap_table_size = 48M

max_allowed_packet = 16777216

join_buffer_size = 64M

tmp_table_size = 64M

innodb_file_per_table = ON

innodb_buffer_pool_size = 240M

innodb_doublewrite = OFF

innodb_lock_wait_timeout = 50

innodb_flush_log_at_timeout = 3

innodb_read_io_threads = 32

innodb_write_io_threads = 16

3.4 配置 SNMP

配置 net-snmp-agent, 修改配置文件 /etc/snmp/snmpd.conf

将41行

com2sec notConfigUser  default       public

改为

com2sec notConfigUser  127.0.0.1     public

将62行

access  notConfigGroup ""      any       noauth    exact  systemview none none

改为

access  notConfigGroup ""      any       noauth    exact  all    none   none

将85行

#view all    included  .1                               80

改为

view all    included  .1                               80

四、安装配置 Cacti

4.1 下载解压 Cacti 安装包

# cd /var/www/html

# wget http://www.cacti.net/downloads/cacti-1.1.23.tar.gz

# tar zxvf cacti-1.1.23.tar.gz

4.2 创建 cacti 数据库,创建 cactiuser 用户,设置相关授权

# mysql -u root -p

MariaDB [(none)]> CREATE database cacti default character set utf8;

MariaDB [(none)]> create user 'cactiuser'@'localhost' identified by 'cactiuser';

MariaDB [(none)]> grant all privileges on cacti.* to cactiuser@localhost;

MariaDB [(none)]> grant select on mysql.time_zone_name to 'cactiuser'@'localhost' identified by 'cactiuser';

MariaDB [(none)]> flush privileges;

MariaDB [(none)]> quit

4.3 导入 Cacti 默认数据库

# mysql -uroot -p cacti < /var/www/html/cacti-1.1.23/cacti.sql

4.4 编辑 /var/www/html/cacti-1.1.23/include/config.php,给Cacti指定数据库类型,名称,用户名,密码等

$database_type = "mysql";

$database_default = "cacti";

$database_hostname = "localhost";

$database_username = "cactiuser";

$database_password = "cactiuser";

/* load up old style plugins here */

$plugins = array();

//$plugins[] = 'thold';

4.5 创建 cacti 系统用户,设置 graph/log 目录权限

# useradd -r -M cacti

# ln -s /var/www/html/cacti-1.1.23 /var/www/html/cacti

# chown -R apache.apache /var/www/html/cacti/

# chown -R cacti /var/www/html/cacti-1.1.23/{rra,log}/

4.6 添加定时任务

# crontab -e

*/5 * * * * /usr/bin/php /var/www/html/cacti/poller.php > /dev/null 2>&1

五、配置防火墙

5.1 配置 firewalld 防火墙

# firewall-cmd --permanent --add-port=3306/tcp

# firewall-cmd --permanent --add-service=http

# firewall-cmd --permanent --add-port=161/udp

# firewall-cmd --permanent --add-port=162/udp

# firewall-cmd --reload

5.2 关闭selinux

# setenforce 0

# sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config

六、设置开机启动相关服务

# systemctl enable httpd

# systemctl enable mariadb

# systemctl enable crond

# systemctl enable snmpd

七、重启相关服务

# systemctl restart httpd

# systemctl restart mariadb

# systemctl restart crond

# systemctl restart snmpd

八、登录 Cacti Web 安装界面

使用浏览器登录

http://your-server/cacti/

勾选 “Accept GPL License Agreement” –> Next –> Next –> 下一步 –> Next –> Next –> 勾选所有预置模版 –> Finish

使用初始登录用户名和密码(admin)登录,然后更改初始密码。

进入Cacti Web 管理界面。

九、安装 spine

9.1 下载解压与 Cacti 相同版本号 Spine 源码安装包

# cd /usr/local/src

# wget http://www.cacti.net/downloads/spine/cacti-spine-1.1.23.tar.gz

# tar xf cacti-spine-1.1.23.tar.gz

9.2 源码编译安装 spine

# ln -s /usr/lib64/libmysqlclient.so.18.0.0 /usr/lib64/libmysqlclient.so

# ntpdate 202.120.2.101

# cd cacti-spine-1.1.23

# ./configure && make && make install

9.3 复制修改配置文件

# cp /usr/local/spine/etc/spine.conf.dist /usr/local/spine/etc/spine.conf

修改spine.conf文件内用户名及密码配置

DB_Host                 localhost

DB_Database             cacti

DB_User                 cactiuser

DB_Pass                 cactiuser

DB_Port                 3306

RDB_Host                localhost

RDB_Database            cacti

RDB_User                cactiuser

RDB_Pass                cactiuser

RDB_Port                3306

9.4 登录 Cacti WEB 页面修改 Cacti 配置

Console -> 设置(Cacti Settings) -> Paths -> 在 Spine Binary File Location 文本框内填入配置文件路径 /usr/local/spine/bin/spine ,在 Spine Config File Path 文本框内填入配置文件路径 /usr/local/spine/etc/spine.conf,点击保存 (Save)。

Console -> 设置(Cacti Settings) -> Poller -> 在 Poller Type 下拉框中选择 spine ,点击保存 (Save)。

十、特别提示

在添加完设备和图形后,需要重建采集器缓存。

Console –> 系统工具 –> Rebuild Poller Cache

awk(报告生成器),grep(文本过滤器),sed(流编辑器)使用入门

未分类

linux下的文本三剑客

grep

egrep,grep,fgrep 
文本查找的需要
grep:根据模式搜索文本,并将符合模式的文本行显示出来。
pattern:文本符和正则表达式的元字符组合而成的匹配条件

grep [option] "pattern" file 
grep root /etc/passwd

-i:忽略大小写 
--color:匹配的字符高亮显示  alias
alias  grep='grep --color'
-v:反向查找 
-o:只显示被模式匹配的字符串(不显示行)

globbing

*:任意长度的任意字符
?:任意单个字符
[]:任意一个字符
[^]:其中任意一个非

正则表达式:Regular ExPression,REGEXP

元字符:
.:匹配任意单个字符
[]:匹配指定范围内的任意字符
[^]:匹配指定范围内的任意单个字符
[:digit:][:lower:][:upper:] []

字符匹配次数:
*:表示匹配前面的字符任意次(0-inf)
   a*b 
   a.*b
.*:表示任意长度的,任意字符
工作在贪婪模式 
?:匹配其前面的字符一个或0次。
    部分匹配 
  a?b 
{m,n}:匹配其前的字符至少m,至多n次。
   {1,}
  {0,3}
  a{1,3}
  a.{1,3}

位置锚定:

^:锚定行首,此字符后面的任意内容必须出现在行首。
grep "^root" /etc/passwd 

$:锚定行尾,此字符前面的任意内容必须出现在行尾。

grep "bash$" /etc/passwd 
^$:空白行 
grep '^$' /etc/passwd

数字:

[0-9]:

grep "[[:space:]][[:digit:]]$" 

r555t 

锚定单词:

<或b:其后面的任意字符必须出现在行首
>或b:其前面的任意字符必须出现在行尾。

This is root.
The user is mroot
rooter is dogs name.
chroot is a command.
grep "root>" test.txt 
grep "<root" test.txt 
grep "<root>" test.txt  

分组:

()
(ab)* :ab一个整体 

  后向引用

He love his lover.
She like her liker.
He  love his liker.
She like her lover.

grep 'l..e*l..e*' text.txt 
grep "l..e.*1" text.txt
grep "(l..e)" 

1:调用第一个左括号以及与之对应的右括号之间的内容。
2:
3:

/etc/inittab 
grep '([0-90]).*1$'  /etc/inittab 

REGEXP:regular Expresssion

pattern:文本的过滤条件

正则表达式:
basic REGEXP : 基本正则表达式
Extent REGEXP :扩展正则表达式

基本正则表达式

.
[]
[^]

次数匹配:
*:
?:0或1次
{m,n}:至少m次,至多n次

.*:

锚定:
^:
$:
<,b: 
>,b:

()
1,2....

grep:使用基本的正则表达式定义的模式来过滤文本的命令:

-i:忽略大小写 
-v 
-o 
--color 

-E 支持扩展的正则表达式 
-A  # :显示匹配行及以后多少行也显示 
  after 
-B:显示匹配行以及前面的n行
   before 
-C:显示匹配行以及前后的n行
   contest 
grep -A 2 ""  file 


扩展正则表达式:
   贪婪模式

字符匹配:
.
[]
[^]

次数匹配:
*:
?:
+:匹配其前面的字符至少一次
{m,n}

位置锚定:
^
$
<
>

分组:
():分组
1,2,3.....

或者:
a|b  or 

C|cat: 
(C|c)at: 

grep --color -E '^[[:space:]]+' /boot/grub/grub.conf 

grep -E = egrep 

egrep --color '<([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-5][0-9]|25[0-5])>' 

(<([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-5][0-9]|25[0-5])>.){3}'<([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-5][0-9]|25[0-5])>.'

IPV4:
5类:
A B C D E 
A:1-127 
B:128-191 
C: 192--223 

<[1-9]|[1-9][0-9]|1[0-9]{2}|2[01][0-9]|22[0-30]>

sed(流编辑器)

sed基本用法:

sed:stream Editor 
行编辑器 
   文本编辑器 
   逐行处理文本 

全屏编辑器:vim 

内存空间:模式空间 
sed 模式空间 
匹配模式空间后,进行操作,将结果输出。仅对模式空间中的数据进行处理,而后,处理结束,将模式空间打印至屏幕;

默认sed不编辑原文件,仅对模式空间中的数据进行处理。

sed [option] [sed-scripts]

option:

-n:静默模式 
-i:直接修改原文件
-e scripts -e script:可以同时执行多个脚本。
-f /path/to/sed_scripts  命令和脚本保存在文件里调用。
  sed -f /path/to/scripts  file 
-r:表示使用扩展的正则表达式。
   只是进行操作,不显示默认模式空间的数据。

comamnd:

address:指定处理的行范围

sed 'addressCommand' file ... 
对符合地址范围进行操作。
Address: 
1.startline,endline 
 比如1,100
   $:最后一行
2./RegExp/ 
  /^root/
3./pattern1/,/pattern2/ 
  第一次被pattern匹配到的行开始,至第一次pattern2匹配到的行结束,这中间的所有行。
4.LineNumber 
   指定行 
5.startline,+N 
 从startline开始,向后的N行。

Command:
 d:删除符合条件的行。
     sed '3,$d' /etc/fstab
     sed '/oot/d' /etc/fstab 
注意:模式匹配,要使用 // 
    sed '1d' file 
p:显示符合条件的行 
 sed '/^//d' /etc/fstab 
 sed '/^//p' /etc/fstab 
   会显示两次
    先显示P匹配,再显示所有模式空间的数据。
a string:在指定的行后面追加新行,内容为"string"
sed '/^//a # hello world' /etc/fstab 
添加两行:
sed '/^//a #hello world n #hi' /etc/fstab 

i sting:在指定行的前面添加新行,内容为string。

r file:将指定的文件的内容添加在指定行后面。
  sed '2r /etc/issue'   /etc/fstab 
  sed '$r /etc/issue' /etc/fstab 

w file:将地址指定的范围的内容另存至另一文件中。
 sed '/oot/w /tmp/oot.txt' /etc/fstab 

s/pattern/string/:查找并替换 
     sed  's/oot/OOT/'  /etc/fstab 
sed 's/^//#/' /etc/fstab 
sed 's///#/'/etc/fstab 仅替换每一行第一次被模式匹配的串。
  加修饰符 
   g:全局替换 
   i:忽略大小写 
 sed 's///#/g'/etc/fstab

 s///:s###
 s@@@

sed 's#+##' 

后向引用

l..e:like----->liker 
     love----->lover 

sed 's#l..e#&r#' file
&:表示模式匹配的引用 

sed 's#l..e#1r#' file 

like---->Like
love---->Love 
sed 's#l(..e)#L1#g' file 


history |sed 's#[[:space:]]##g'
history | sed 's#^[[:space:]]##g'

sed ''dirname

例子????

 1.删除/etc/grub.conf文件中行首的空白符;
 sed  's/^[[:space:]]+//g' /etc/grub.conf 
 2.替换/etc/inittab文件中"id:3:initdefault:"一行中的3
 sed 's#id:3:init#id:5:initd#'
 sed 's@(id:)[0-9](:initdefault:)@152@g' /etc/inittab 
 3.删除/etc/inittab文件中的空白行。
  sed '/^$/d' /etc/inittab
4.删除/etc/inittab文件中开头的#号
sed 's/^#//'  
5.删除莫文件中开头的#号以及空白行。
sed 's/^[[:space:]]+//g' 
6.删除某文件中以空白字符后面跟#类的行中开头的空白字符以及#
sed -r 's/^[[:space:]]+#//g' 
7.取出一个文件路径的目录名称
echo '/etc/rc.d'|sed -r 's@^(/.*/)[^/]+/?@1@g'

awk(报告生成器)

grep :文本过滤器
sed:流编辑器 


grep option pattern file 
sed addresscommmand file 
sed 'comand/pattern/' file 

awk(报告生成器)

根据定义好的格式,显示出来。
nawk 
gawk
gnu awk 

awk option 'script' file file2 
awk [option] 'pattern {action}' file file2 

print 
printf 自定义显示格式


awk一次抽取一行,然后对每一行进行切割分片,每一片可以使用变量进行引用。
$0:表示引用一整行
$1:第一个切片
$2:第二个切片 

awk '{print $1}' text.txt 
awk '{print $1,$2}' text.txt

选项:

-F  指定分隔符
awk -F ''

awk 'BEGIN{OPS="#"}{print $1,$2}' test.txt
BEGIN{OPS=""} 输出分隔符

输出特定字符
awk '{print $1,"hello",$2,$3,$4,$5}' file 

awk 'BEGIN{print "line onenline twonline tree"}'

print的格式:
print item1,item2...

awk -F: 输入分隔符 
OFS="#"   输出分隔符

awk变量

awk内置变量
FS: filed separator,读取文本时,所用字段分隔符
RS:recordsepartor,输入文本信息所使用的换行符。
OFS:OUT filed separator 
ORS:Output ROw separator 

awk -F:
OFS="#"
FS=":"

awk内置变量之数据变量

NR: the number of input record ,awk命令所处理的记录,如果有多个文件,这个数据是所有处理的行数。
FNR:当前文件所处理的行是本文件第多少行。
NF:当前所处理的行有多少个字段。


awk '{print NF}' file 
awk '{print $NF}' file 
awk '{print NR}' file 

-v 定义变量

awk -v test="hello awk" '{print test}' 
awk -v test="hell awk" 'BEGIN{print test}'


awk  'BEGIN{test='hello awk',print test}'

printf 格式化显示

printf  format,item1,item2...

awk 'BEGIN{printf %c,}'
注意:printf不换行  

%d 
%e 
%f 
%g 

修饰符
-:左对齐 
%nd:显示宽度 
awk '{printf %-10s%-10sn,$1,$2}' file

awk的操作符

算术操作符

字符串操作符

布尔表达式

x < y 
x <= y 
x > y 
x != y 
x ~ y 匹配 
x !~ y 

表达式间的逻辑关系符

&& 
||

条件表达式

select?if-true-exp:if-false-exp 
a>b?a=1:b=2 

awk模式

1.正则表达式 /pattern/
2.表达式 
3.REGEXP 指定匹配范围 
4.BEGIN/END 
5Empty  


awk -F : '/^r/ {print $1}' /etc/passwd 
awk -F : '$3>=500{printf $1,$3}' /etc/passwd 
awk -F: '$3+1>=500{print $1,$3}' /etc/passwd

awk -F: '$7~"bash$"{print $1,$7}' /etc/passwd 
进行匹配测试
awk -F: '$7!~"bash$"{print $1,$7}' /etc/passwd 

awk -F: '/^r/,/^m/{print $1,$7}' /etc/passwd 

awk -F: '$3==0,$7~"bash"{print $1,$3,$7}' /etc/passwd 

awk -F '{printf "%-10s%-10s%-20sn",$1,$2,$3}' /etc/passwd 

BEGIN ,END 

awk -F: '$3==0,$7~"nologin"BEGIN{print "Username       ID    shell"}{printf "%-10s%-10s%-20sn"$1,$3,$7} END{print "ending"}' /etc/passwd 

action

1.ExPression 
2.control statements 
3.compound statements 
4.INput statment 
5 output statements 

控制语句

if-else

if(condition) {then-body} else {[else-body]}
eg:
awk -F:

while

while (condition){statement1;statement2;...}
循环每一字段 
length([string])

awk -F: '{i=1; while (1<=NF) if {(length($i)>4) {print $i}; i++}}'

df -hP |awk '{if($4 >=) Print $0}'


do while 
do{statement1,statement2,...} while(condition)

for 
for( ; ; ){statement1;statement2....}

awk -F: '{for(i=1:i<=NF;i++){if(length($i)>=4){print $i}}}'  /etc/passwd 

case 
switch (exprssion) {case value or /regexp/:statement1,statement2,...default:statement,....}

break和continue 
contine是遍历字段的 

next 
提前结束对本行文本的处理,并接着处理下一行,

数组

数组下表是从1开始的
awk[mon]=1 
awk[tus]=2 


for (var in arrary){statement,....}

awk -F: '{shell[$NF]++}END {for(A in shell) {print A,shell[A]}}' /etc/passwd 

nestat -tan 

netstat -tan |awk '/^tcp/{STATE[$NF]++}END{for (S in STATE){print S,STATE[S]}}'

awk '{count[$1]++}END{for ip in count}{printf "%-20s:%dn",ip,count[ip]}}'  access_log

Linux Awk使用案例总结 nginx日志统计

知识点:

  • 数组

数组是用来存储一系列值的变量,可通过索引来访问数组的值。
Awk中数组称为关联数组,因为它的下标(索引)可以是数字也可以是字符串。
下标通常称为键,数组元素的键和值存储在Awk程序内部的一个表中,该表采用散列算法,因此数组元素是随机排序。

数组格式:array[index]=value

一、Nginx日志分析

日志格式

'$remote_addr - $remote_user [$time_local] "$request" $status $request_body  $body_bytes_sent "$http_referer"  "$http_user_agent" "$http_x_forwarded_for"

日志记录:

183.251.21.109 - - [16/Sep/2017:09:43:36 +0800] "POST /article/getKeywords HTTP/1.1" 200 str=Linux+Awk%E4%BD%BF%E7%94%A8%E6%A1%88%E4%BE%8B%E6%80%BB%E7%BB%93+nginx%E6%97%A5%E5%BF%97%E7%BB%9F%E8%AE%A1  185 "http://xxxx" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36" "-"

1、统计日志中访问最多的10个IP

思路:对第一列进行去重,并输出出现的次数

方法1:

awk '{a[$1]++}END{for(i in a)print a[i],i|"sort -k1 -nr|head -n10"}' access.log

方法2:

awk '{print $1}' access.log |sort |uniq -c |sort -k1 -nr |head -n10

说明:a[$1]++ 创建数组a,以第一列作为下标,使用运算符++作为数组元素,元素初始值为0。处理一个IP时,下标是IP,元素加1,处理第二个IP时,下标是IP,元素加1,如果这个IP已经存在,则元素再加1,也就是这个IP出现了两次,元素结果是2,以此类推。因此可以实现去重,统计出现次数。

2、统计日志中访问大于100次的IP

方法1:

awk '{a[$1]++}END{for(i in a){if(a[i]>100)print i,a[i]}}' access.log

方法2:

awk '{a[$1]++;if(a[$1]>100){b[$1]++}}END{for(i in b){print i,a[i]}}' access.log

说明:方法1是将结果保存a数组后,输出时判断符合要求的IP。方法2是将结果保存a数组时,并判断符合要求的IP放到b数组,最后打印b数组的IP。

3、统计2016年4月9日一天内访问最多的10个IP

思路:先过滤出这个时间段的日志,然后去重,统计出现次数

方法1:

awk '$4>="[9/Apr/2016:00:00:01" && $4<="[9/Apr/2016:23:59:59" {a[$1]++}END{for(i in a)print a[i],i|"sort -k1 -nr|head -n10"}' access.log

方法2:

sed -n '/[9/Apr/2016:00:00:01/,/[9/Apr/2016:23:59:59/p' access.log |sort |uniq -c |sort -k1 -nr |head -n10  #前提开始时间与结束时间日志中必须存在

4、统计当前时间前一分钟的访问数

思路:先获取当前时间前一分钟对应日志格式的时间,再匹配统计

date=$(date -d '-1 minute' +%d/%b/%Y:%H:%M);awk -vdate=$date '$0~date{c++}END{print c}' access.log
date=$(date -d '-1 minute' +%d/%b/%Y:%H:%M);awk -vdate=$date '$4>="["date":00" && $4<="["date":59"{c++}END{print c}' access.log
grep -c $(date -d '-1 minute' +%d/%b/%Y:%H:%M) access.log

说明:date +%d/%b/%Y:%H:%M –> 09/Apr/2016:01:55

5、统计访问最多的前10个页面($request)

awk '{a[$7]++}END{for(i in a)print a[i],i|"sort -k1 -nr|head -n10"}' access.log

6、统计每个URL访问内容的总大小($body_bytes_sent)

awk '{a[$7]++;size[$7]+=$10}END{for(i in a)print a[i],size[i],i}' access.log

7、统计每个IP访问状态码数量($status)

awk '{a[$1" "$9]++}END{for(i in a)print i,a[i]}' access.log

8、统计访问状态码为404的IP及出现次数

awk '{if($9~/404/)a[$1" "$9]++}END{for(i in a)print i,a[i]}' access.log

AWK分析nginx访问日志

如题,记录一次 awk 应用。通过分析 nginx 访问日志,得出 pv、uv等数据。

背景

前段时间被要求通过分析 nginx 的访问日志得出项目的 pv、uv 和来源设备等占比。主要是该平台下面有多个 h5 子项目,每个子项目又是一个简单的 SPA 应用,所以就没有考虑到第三方统计平台。不过这个完成了好久,现在才写,主要还是个人懒癌发作。。

AWK

首先介绍下 AWK 吧。

AWK 是一种 linux/unix 下的编程语言,强大的文本语言处理工具。它仅仅需要几行代码就能够完成复杂的文本处理工作。AWK 模式如下:

awk 'BEGIN{ commands } pattern{ commands } END{ commands }'

网上找到一张对应的流程图如下:

未分类

BEGIN语句块在awk开始从输入流中读取行之前被执行,这是一个可选的语句块,比如变量初始化、打印输出表格的表头等语句通常可以写在BEGIN语句块中。

END语句块在awk从输入流中读取完所有的行之后即被执行,比如打印所有行的分析结果这类信息汇总都是在END语句块中完成,它也是一个可选语句块。

pattern语句块中的通用命令是最重要的部分,它也是可选的。如果没有提供pattern语句块,则默认执行 { print },即打印每一个读取到的行,awk读取的每一行都会执行该语句块。

基本语法

AWK 的基本语法可参考: http://man.linuxde.net/awk

处理 nginx 日志

默认的 nginx 日志输出少了些我们需要的部分,所以我们需要先处理 nginx.conf 配置,打出我们需要的日志。这里先说下信息的统计思路吧。PV 很简单,每条 html 的访问记录算一条 PV,每个时间段的 PV 通过 nginx 日志里面的访问时间来判断,每天的 PV 通过将 nginx 日志切割,即每天的访问日志放到每天对应的文件里来判断。而 UV 则通过 cookie 来鉴别,即首先通过 javascript 在客户端种一个唯一的 cookie,每次访问记录中没有携带 cookie 的访问算一次 UV,客户端 cookie 的有效期到每天的0点。而来源及访问设备等可以通过浏览器携带的信息来判断。

nginx 配置

我们采取的部分 nginx.conf 配置如下:

http {
    include       mime.types;
    default_type  application/octet-stream;
    // 定义一种 project_log 格式的日志模式
    log_format project_log '$remote_addr - $time_local "$request" $status $sent_http_content_type UV: "$guid" $http_user_agent';
    sendfile        on;
    keepalive_timeout  65;
    client_max_body_size 2m;
    fastcgi_intercept_errors on;
    server {
    listen 443;
    server_name localhost;
    ssl on;
        ssl_certificate /test/server.crt;
    ssl_certificate_key /test/server.key;
    // 通过 cookie 正则匹配,如果有 _qingguoing 形式的 cookie 记录,就打到 log 中,默认是空
    if ($http_cookie ~* "_qingguoing=([A-Z0-9.]*)" ) {
        set $guid $1;
    }
    if ( $request_method !~ GET|POST|HEAD|DELETE|PUT ) {
            return 403;
        }
    location /qingguoing/ {
            index index.html index.htm;
            error_page 404 ./404.html;
            // 日志输出到 logs/qingguoing.log 文件
            access_log logs/qingguoing.log project_log;
            if (!-e $request_filename){
                return 404;
            }
        }
        error_page  404              /404.html;
    }
}

nginx 日志切割脚本

#nginx 日志切割脚本
#!/bin/bash
# 设置日志文件存放目录
logs_path="/var/www/logs/";
#设置pid文件
pid_path="/var/www/logs/nginx.pid";
#重命名日志文件
mv ${logs_path}qingguoing.log ${logs_path}logs_daily/qingguoing_$(date -d "yesterday" +"%Y%m%d").log
#向ngin主进程发信号重新打开日志
kill -USR1 `cat ${pid_path}`

AWK 处理脚本

BEGIN {
}
{
    # 过滤错误请求,只处理 html 200 请求,因为 html 没设缓存
    if ($8 != 200) next;
    if ($9 == "text/html") {
        # 来源匹配
        fromIndex = index($6, "from=");
        endIndex = index($6, "&");
        if (fromIndex != 0 && endIndex == 0) {
            # 增加 from= 的长度
            origin = substr($6, fromIndex + 5);
        } else {
            origin = substr($6, fromIndex + 5, endIndex);
        }
        # pathname 匹配
        pathLength = split($6, path, "/");
        # uv标记
        uv = 0;
        project = path[3];
        # 404.html
        if (pathLength > 4 || project == "404.html") next;
        if (origin != "platform") {
            # 日期 时间
            split($3, datetime, ":");
            if (timeArr[project] && timeArr[project] != datetime[2]) {
                # 新的一条记录,数据存到 mongodb 中并清空
                system("mongo 'yshow' --eval 'var project=""project"", date=""dateArr[project]"", time=""timeArr[project]"", pv=""pvArr[project]"", uv=""uvArr[project]"", pvFromOther=""pvFromOtherArr[project]"", pvFromTimeline=""pvFromTimelineArr[project]"", pvFromGroupmsg=""pvFromGroupmsgArr[project]"", pvFromSinglemsg=""pvFromSinglemsgArr[project]"",  uvFromOther=""uvFromOtherArr[project]"", uvFromTimeline=""uvFromTimelineArr[project]"", uvFromGroupmsg=""uvFromGroupmsgArr[project]"", uvFromSinglemsg=""uvFromSinglemsgArr[project]"", pvIos=""pvIosArr[project]"", uvIos=""uvIosArr[project]"", pvAdr=""pvAdrArr[project]"", uvAdr=""uvAdrArr[project]"", pvDeviceOther=""pvDeviceOtherArr[project]"", uvDeviceOther=""uvDeviceOtherArr[project]""' ./logs_mongo_insert.js");
                delete dateArr[project];
                delete timeArr[project];
                delete pvArr[project];
                delete uvArr[project];
                delete pvFromOtherArr[project];
                delete pvFromTimelineArr[project];
                delete pvFromGroupmsgArr[project];
                delete pvFromSinglemsgArr[project];
                delete uvFromOtherArr[project];
                delete uvFromTimelineArr[project];
                delete uvFromGroupmsgArr[project];
                delete uvFromSinglemsgArr[project];
                delete pvIosArr[project];
                delete uvIosArr[project];
                delete pvAdrArr[project];
                delete uvAdrArr[project];
                delete pvDeviceOtherArr[project];
                delete uvDeviceOtherArr[project];
            }
            # save
            timeArr[project] = datetime[2];
            dateArr[project] = datetime[1];
            pvArr[project]++;
            if ($11 == """") {
                uvArr[project]++;
                uv = 1;
            }
            ios = index($0, "iPhone");
            adr = index($0, "Android");
            # 设备
            if (ios > 0) {
                if (uv) uvIosArr[project]++;
                pvIosArr[project]++;
            } else if (adr > 0) {
                if (uv) uvAdrArr[project]++;
                pvAdrArr[project]++;
            } else {
                if (uv) uvDeviceOtherArr[project]++;
                pvDeviceOtherArr[project]++;
            }
            # 其他
            if (fromIndex == 0 ) {
                if (uv) uvFromOtherArr[project]++;
                pvFromOtherArr[project]++;
            }
            # 微信朋友圈
            if (origin == "timeline") {
                if (uv) uvFromTimelineArr[project]++;
                pvFromTimelineArr[project]++;
            }
            # 微信群
            if (origin == "groupmessage") {
                if (uv) uvFromGroupmsgArr[project]++;
                pvFromGroupmsgArr[project]++;
            }
            # 好友分享
            if (origin == "singlemessage") {
                if (uv) uvFromSinglemsgArr[project]++;
                pvFromSinglemsgArr[project]++;
            }
        }
    };
}
END {
    for (project in pvArr) {
        system("mongo 'yshow' --eval 'var project=""project"", date=""dateArr[project]"", time=""timeArr[project]"", pv=""pvArr[project]"", uv=""uvArr[project]"", pvFromOther=""pvFromOtherArr[project]"", pvFromTimeline=""pvFromTimelineArr[project]"", pvFromGroupmsg=""pvFromGroupmsgArr[project]"", pvFromSinglemsg=""pvFromSinglemsgArr[project]"",  uvFromOther=""uvFromOtherArr[project]"", uvFromTimeline=""uvFromTimelineArr[project]"", uvFromGroupmsg=""uvFromGroupmsgArr[project]"", uvFromSinglemsg=""uvFromSinglemsgArr[project]"", pvIos=""pvIosArr[project]"", uvIos=""uvIosArr[project]"", pvAdr=""pvAdrArr[project]"", uvAdr=""uvAdrArr[project]"", pvDeviceOther=""pvDeviceOtherArr[project]"", uvDeviceOther=""uvDeviceOtherArr[project]""' ./logs_mongo_insert.js");
    }
}

至此基本逻辑已写完。只需要在服务器里设置定时任务就行,例如每天的0点切割nginx日志,0点30开始处理前一天的日志存储统计信息。所以当前该思路唯一也是最大的缺陷就是没办法获取实时访问记录。后续如果有新思路会及时补充。

至此基本逻辑已写完。只需要在服务器里设置定时任务就行,例如每天的0点切割nginx日志,0点30开始处理前一天的日志存储统计信息。所以当前该思路唯一也是最大的缺陷就是没办法获取实时访问记录。

最后

前面也说了,最近才写这篇文章的主要原因是懒癌,导火索是前两天想通过 AWK 处理一个 hosts 文件到 charles 的 DNS 记录里,居然忘了 AWK 该怎么写了,果然好记性不如烂笔头。

本文代码地址:https://github.com/qingguoing/test/tree/master/awk/nginx

hosts 文件转 charles DNS 记录文件:https://github.com/qingguoing/test/tree/master/awk/DNS

awk 自带函数用法

一、split 初始化和类型强制

awk的内建函数split允许你把一个字符串分隔为单词并存储在数组中。你可以自己定义域分隔符或者使用现在FS(域分隔符)的值。
格式:

   split (string, array, field separator)
   split (string, array)  -->如果第三个参数没有提供,awk就默认使用当前FS值。

例子:

例1:替换分隔符

time="12:34:56"
out=`echo $time | awk '{split($0,a,":");print a[1],a[2],a[3]}'`
echo $out

例2:计算指定范围内的和(计算每个人1月份的工资之和)

[root@test ~]# cat test.txt
Tom    2012-12-11      car     53000
John   2013-01-13      bike    41000
vivi    2013-01-18      car     42800
Tom    2013-01-20      car     32500
John   2013-01-28      bike    63500
[root@test ~]# awk '{split($2,a,"-");if(a[2]==01){b[$1]+=$4}}END{for(i in b)print i,b[i]}' test.txt  
vivi         42800
John   104500
Tom    32500

二、substr 截取字符串

返回从起始位置起,指定长度之子字符串;若未指定长度,则返回从起始位置到字符串末尾的子字符串。
格式:

  substr(s,p) 返回字符串s中从p开始的后缀部分
  substr(s,p,n) 返回字符串s中从p开始长度为n的后缀部分

例子:

[root@test ~]# echo "123" | awk '{print substr($0,1,1)}'
1

解释:

awk -F ',' '{print substr($3,6)}'    --->  表示是从第3个字段里的第6个字符开始,一直到设定的分隔符","结束.
substr($3,10,8)  --->  表示是从第3个字段里的第10个字符开始,截取8个字符结束.
substr($3,6)     --->  表示是从第3个字段里的第6个字符开始,一直到结尾

三、length 字符串长度

length函数返回没有参数的字符串的长度。length函数返回整个记录中的字符数。

[root@test ~]# echo "123" | awk '{print length}'
3

四、gsub函数

gsub函数则使得在所有正则表达式被匹配的时候都发生替换。gsub(regular expression, subsitution string, target string);简称 gsub(r,s,t)。

举例:把一个文件里面所有包含 abc 的行里面的 abc 替换成 def,然后输出第一列和第三列

awk '$0 ~ /abc/ {gsub("abc", "def", $0); print $1, $3}' abc.txt

解决 apt 错误为 Could not open file(Permission denied) 的更新失败

错误信息看起来就是这样:

Reading package lists… Done
E: Failed to fetch http://au.archive.ubuntu.com/ubuntu/dists/xenial/main/dep11/Components-amd64.yml
Could not open file /var/lib/apt/lists/partial/au.archive.ubuntu.com_ubuntu_dists_xenial_main_dep11_Components-amd64.yml.gz – open (13: Permission denied) [IP: 202.158.214.106 80]
E: Failed to fetch http://au.archive.ubuntu.com/ubuntu/dists/xenial-backports/main/dep11/icons-64×64.tar Could not open file /var/lib/apt/lists/partial/au.archive.ubuntu.com_ubuntu_dists_xenial-backports_main_dep11_icons-64×64.tar.gz – open (13: Permission denied) [IP: 202.158.214.106 80]
E: Some index files failed to download. They have been ignored, or old ones used instead.

原因一般为之前某次更新中断,有的文件处于下载了一部分的状态。解决办法很简单,rm /var/lib/apt/lists/partial/* 即可,注意需要 root 权限。

CentOs中Apache开启mod_rewrite模块

今天刚发现wordpress的固定链接功能,于是想试试水。。

未分类

但是改完之后发现只有首页能进,文章都进不去了,404 NOT FOUND,崩溃。。。

于是Google了一下,在这里把方法整理一下

之所以无法访问 是因为Apache的 rewrite模块没有开启

rewrite是Apache环境的一个伪静态功能,如果我们没有让Apache开启rewrite功能,网站上所有的rewrite规则都不可使用.

自然文章链接的重写也不行咯

CentOs的配置文件放在: /etc/httpd/conf/httpd.conf

打开文件找到:

LoadModule rewrite_module modules/mod_rewrite.so

将前面”#”去掉,如果没有的话添加上去

如果你的网站是根目录的话:

<Directory />
Options FollowSymLinks
AllowOverride None
</Directory>

将None改成All

我的网站是放在/var/www/html下面的,所以:

未分类

然后重启Apache服务

输入以下命令:

service httpd restart

未分类

完美。。

补充:如果以上步骤你都做对了 但是还是404的错误 并提示你要手动配置.htaccess文件

请把下面的文件下载好 上传到你网站的根目录下面。

https://pan.baidu.com/s/1qYoKOcG
提取密码:wyru