iptables中DNAT、SNAT和MASQUERADE的理解

DNAT(Destination Network Address Translation,目的地址转换) 通常被叫做目的映谢。而SNAT(Source Network Address Translation,源地址转换)通常被叫做源映谢。

这是我们在设置Linux网关或者防火墙时经常要用来的两种方式。以前对这两个都解释得不太清楚,现在我在这里解释一下。

首先,我们要了解一下IP包的结构,如下图所示:

未分类

在任何一个IP数据包中,都会有Source IP Address与Destination IP Address这两个字段,数据包所经过的路由器也是根据这两个字段是判定数据包是由什么地方发过来的,它要将数据包发到什么地方去。而iptables的DNAT与SNAT就是根据这个原理,对Source IP Address与Destination IP Address进行修改。

然后,我们再看看数据包在iptables中要经过的链(chain):

未分类

图中正菱形的区域是对数据包进行判定转发的地方。在这里,系统会根据IP数据包中的destination ip address中的IP地址对数据包进行分发。如果destination ip adress是本机地址,数据将会被转交给INPUT链。如果不是本机地址,则交给FORWARD链检测。

这也就是说,我们要做的DNAT要在进入这个菱形转发区域之前,也就是在PREROUTING链中做,比如我们要把访问202.103.96.112的访问转发到192.168.0.112上:

iptables -t nat -A PREROUTING -d 202.103.96.112 -j DNAT --to-destination 192.168.0.112

这个转换过程当中,其实就是将已经达到这台Linux网关(防火墙)上的数据包上的destination ip address从202.103.96.112修改为192.168.0.112然后交给系统路由进行转发。

而SNAT自然是要在数据包流出这台机器之前的最后一个链也就是POSTROUTING链来进行操作

iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -j SNAT --to-source 58.20.51.66

这个语句就是告诉系统把即将要流出本机的数据的source ip address修改成为58.20.51.66。这样,数据包在达到目的机器以后,目的机器会将包返回到58.20.51.66也就是本机。如果不做这个操作,那么你的数据包在传递的过程中,reply的包肯定会丢失。

假如当前系统用的是ADSL/3G/4G动态拨号方式,那么每次拨号,出口IP都会改变,SNAT就会有局限性。

iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -o eth0 -j MASQUERADE

重点在那个『 MASQUERADE 』!这个设定值就是『IP伪装成为封包出去(-o)的那块装置上的IP』!不管现在eth0的出口获得了怎样的动态ip,MASQUERADE会自动读取eth0现在的ip地址然后做SNAT出去,这样就实现了很好的动态SNAT地址转换。

Jenkins集成Docker

大概过程如下图:

未分类

由于需要用到docker打包镜像,jenkins宿主机上需要安装docker,原先的jenkins server安装在centos6上无法运行docker,所以这里单独用一台centos7安装一个jenkins server。

jenkins的安装方法有多种:

1、下载jar包,直接通过java运行。

2、用tomcat作为容器运行。

3、通过yum安装。

4、通过docker运行。

安装过程都很简单,这里就不详细说明了。

1、安装插件。打开jenkins页面,安装CloudBees Docker Build and Publish plugin和Publish Over SSH Plugin插件。

未分类

2、新建一个项目。

未分类

3、配置项目。

未分类

4、填写项目名称。jenkins的工作空间与这里的项目名称对应,默认在/var/lib/jenkins/workspace下。

未分类

5、拉取代码。填写项目地址和需要拉取的分支,这里用了一个Additional Behabiours,拉取代码会自动创建一个目录并将代码放在该目录下。

未分类

6、build镜像,并将镜像push到harbor镜像库。填写相应的docker、dockerfile以及镜像库地址信息。

这里需要设置docker启动参数。

#vim /etc/sysconfig/docker
OPTIONS='--insecure-registry 172.60.0.107 -H 0.0.0.0:2375 -H unix:///var/run/docker.sock'
#systemctl restart docker

未分类

7、通过脚本发布到k8s。这里需要设置SSH互信(具体过程略..)。

未分类

8、在k8s master上编写发布脚本。

#vim stg-che001-56-waybill.sh

#!/bin/bash

#update the version number
sed -ri "s@(image.*v).*@1${BUILD_NUMBER}@" /root/che001-56-stg/che001-56-waybill.yaml

#Apply the configuration change
kubectl delete -f /root/che001-56-stg/che001-56-waybill.yaml && kubectl create -f /root/che001-56-stg/che001-56-waybill.yaml

9、配置完成后,点击立即构建。

未分类

10、点击Console Output可以查看构建日志。如果最后提示SUCCESS,则表示构建成功。(这里日志太长,只截取了一半)

未分类

未分类

hadoop集群时间同步

测试环境:

192.168.217.130 master master.hadoop
192.168.217.131 node1 node1.hadoop
192.168.217.132 node2 node2.hadoop

一、设置master服务器时间

查看本地时间和时区

[root@master ~]# date
Mon Feb 27 09:54:09 CST 2017

选择时区

[root@master ~]# tzselect

未分类

未分类

 [root@master ~]# cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

修改时间(date -s 00:00:00或者网络同步:apt-get install ntpdate ; ntpdate cn.pool.ntp.org)
写入硬盘时间(hwclock -w)

二、在master服务器上检查时间服务是否安装

[root@node1 ~]# rpm -q ntp
ntp-4.2.4p8-2.el6.x86_64

如果没有安装,用yum安装

[root@node1 ~]# yum install ntp

按上面的安装方式在内网每台服务器上都安装好NTP软件包。
完成后,都需要配置NTP服务为自启动

[root@master ~]# chkconfig ntpd on
[root@master ~]# chkconfig --list ntpd
ntpd           0:off1:off2:on3:on4:on5:on6:off

三、在master上更改相关配置文件

[root@master ~]# vim /etc/ntp.conf 

进行如下修改:

未分类

第一处新增,意思是从IP地址192.168.217.1-192.168.217.254,默认网关255.255.255.0的服务器都可以使用我们的NTP服务器来同步时间

第二处新增,指明互联网和局域网中作为NTP服务器的IP

第三处是修改,将原有注释去掉,是当服务器与公用的时间服务器失去联系时以本地时间为客户端提供时间服务

配置文件修改完成,保存退出,启动服务。

[root@master ~]#  service ntpd start

启动后,一般需要5-10分钟左右的时候才能与外部时间服务器开始同步时间。可以通过命令查询NTPD服务情况。

查看服务连接和监听

[root@master ~]# netstat -tlunp | grep ntp  
udp        0      0 192.168.217.130:123         0.0.0.0:*                               4990/ntpd           
udp        0      0 127.0.0.1:123               0.0.0.0:*                               4990/ntpd           
udp        0      0 0.0.0.0:123                 0.0.0.0:*                               4990/ntpd           
udp        0      0 fe80::20c:29ff:fee7:123     :::*                                    4990/ntpd           
udp        0      0 ::1:123                     :::*                                    4990/ntpd           
udp        0      0 :::123                      :::*                                    4990/ntpd 

重新启动服务

[root@master ~]# service ntpd restart

可设置crontab每天和NTP服务器同步一次(以和互联网时间同步为例)

[root@master ~]# crontab -l
10 23 * * * root (/usr/sbin/ntpdate cn.pool.ntp.org && /sbin/hwclock -w) &> /var/log/ntpdate.log

四、 将其他节点的时间与master进行同步

在其他每一个节点运行命令

[root@node1 ~]# ntpdate master
27 Feb 10:10:15 ntpdate[32724]: adjust time server 192.168.217.130 offset -0.170230 sec
[root@node2 ~]# ntpdate master
27 Feb 10:10:23 ntpdate[30874]: adjust time server 192.168.217.130 offset -0.149563 sec

这时候发现节点间的时间同步了,但ntpdate只在开机运行,我们若要设置为1小时同步一次

[root@node1 ~]# crontab -l
* */1 * * * /usr/sbin/ntpdate master

Haproxy基于ACL做访问控制

haproxy配置文档 https://cbonte.github.io/haproxy-dconv/

基于ACL做访问控制(四层代理)

网络拓扑

未分类

环境

前端HAProxy 172.16.253.108
后端web1    172.16.253.105
后端web2    172.16.252.1
client      172.16.253.177

安装HAProxy

HAProxy

[root@HAProxy ~]# yum install haproxy -y
[root@HAProxy ~]# rpm -ql haproxy
[root@HAProxy ~]# iptables -F
[root@HAProxy ~]# setenforce 0
[root@HAProxy ~]# systemctl enable haproxy
[root@HAProxy ~]# cp /etc/haproxy/haproxy.cfg{,.bak}
[root@HAProxy ~]# vim /etc/haproxy/haproxy.cfg

web1

[root@web1 ~]# yum -y install httpd
[root@web1 ~]# vim /var/www/html/index.html 
<h1> Backend Server 1 </h1>
[root@web1 ~]# systemctl start httpd
[root@web1 ~]# setenforce 0
[root@web1 ~]# iptables -F

web 2

[root@web2 ~]# yum -y install httpd
[root@web2 ~]# vim /var/www/html/index.html 
<h1> Backend Server 2 </h1>
[root@web2 ~]# service httpd start 
[root@web2 ~]# setenforce 0
[root@web2 ~]# iptables -F
  • block阻塞主机访问

172.16.251.196用户访问stats状态界面,并显示错误网页http://172.16.253.108:10080/403.html

HAProxy

[root@HAProxy ~]# vim /etc/haproxy/haproxy.cfg
    frontend myweb *:80
        default_backend websrvs

    backend websrvs
        balance roundrobin
        server srv1 172.16.253.105:80 check weight 2
        server srv2 172.16.252.1:80 check weight 1
    listen stats
        bind *:9000
        acl allowstats src 172.16.251.196
        block if allowstats  \阻塞allowstats中的IP访问stats界面
        errorloc 403 http://172.16.253.108:10080/403.html
        stats enable
        stats uri /myproxy?admin
        stats realm "HAProxy Stats Page"
        stats auth admin:admin
        stats admin if TRUE
[root@HAProxy ~]# systemctl restart haproxy 

访问测试

172.16.251.196使用浏览器访问测试http://172.16.253.108:10080/403.html 
  • http-request允许某主机访问stats状态界面

允许172.16.251.196用户访问http://172.16.253.108服务器的HAProxy的状态界面

HAProxy

[root@HAProxy ~]# vim /etc/haproxy/haproxy.cfg
    frontend myweb *:80
        default_backend websrvs

    backend websrvs
        balance roundrobin
        server srv1 172.16.253.105:80 check weight 2
        server srv2 172.16.252.1:80 check weight 1
    listen stats
        bind *:9000
        acl allowstats src 172.16.251.196
        # http-request allow if allowstats  \允许allowstats中的IP访问stats状态界面
        http-request deny  unless allowstats \除了allowstats之外全部拒绝访问,即仅允许allowstats访问
        # http-request deny if allowstats \拒绝allowstats访问
        errorloc 403 http://172.16.253.108:10080/403.html \错误网页文件
        stats enable
        stats uri /myproxy?admin
        stats realm "HAProxy Stats Page"
        stats auth admin:admin
        stats admin if TRUE
[root@HAProxy ~]# systemctl restart haproxy 

访问测试

图形化浏览器
    172.16.251.196使用浏览器访问测试http://172.16.253.108:10080/403.html    
字符界面 
    [root@client ~]# curl --basic --user admin:admin http://172.16.253.108:9000/myproxy?admin   

基于ACL做访问控制(七层代理)

动态网页存放在动态服务器组中,静态网页存放在静态服务器组中

拓扑环境

环境

前端HAProxy 172.16.253.108
后端web1    172.16.253.105
后端web2    172.16.253.191
client      172.16.253.177
  • web1使用虚拟主机技术搭建两个web server,用来存放动态网页内荣容
  • web2使用虚拟主机搭建两个web server用来替代静态网页内容

web1创建虚拟主机

[root@web1 ~]# yum -y install php httpd
[root@web1 ~]# mkdir /data/web/vhost{1,2} -pv
[root@web1 ~]# vim /data/web/vhost1/index.php
<h1> Application Server 1</h1>
<?php
    phpinfo();
?>
[root@web1 ~]# vim /data/web/vhost2/index.php
<h1> Application Server 2</h1>
<?php
    phpinfo();
?>

虚拟主机1的配置文件
[root@web1 ~]# vim /etc/httpd/conf.d/vhost1.conf \编辑vhost1虚拟主机的配置文件
<VirtualHost *:80>
    ServerName www1.danran.com
    DocumentRoot "/data/web/vhost1"
    <Directory "/data/web/vhost1">
            Options FollowSymLinks \允许使用连接文件目录
            AllowOverride None \不允许其他配置文件覆盖此文件中的设置
            Require all granted
    </Directory>
</VirtualHost>

虚拟主机2的配置文件
[root@web1 ~]# vim /etc/httpd/conf.d/vhost2.conf
[root@web1 ~]# vim /etc/httpd/conf.d/vhost2.conf
Listen 8080
<VirtualHost *:8080>
    ServerName www2.danran.com
    DocumentRoot "/data/web/vhost2"
    <Directory "/data/web/vhost2">
            Options FollowSymLinks
            AllowOverride None
            Require all granted
    </Directory>
</VirtualHost>

[root@web1 ~]# systemctl restart httpd.service 
[root@web1 ~]# ss -ntl

web2创建虚拟主机

[root@web2 ~]# yum -y install httpd
[root@web2 ~]# mkdir -pv /data/web/vhost{1,2}
[root@web2 ~]# find /usr/share/ -iname "*.jpg" -exec cp {} /data/web/vhost1/ ;
[root@web2 ~]# find /usr/share/ -iname "*.jpg" -exec cp {} /data/web/vhost2/ ;
[root@web2 ~]# vim /data/web/vhost1/index.html
<h1> Image Server 1 </h1>
[root@web2 ~]# vim /data/web/vhost2/index.html
<h1> Image Server 2 </h1>

编辑虚拟主机1的配置文件
[root@web2 ~]# vim  /etc/httpd/conf.d/vhost1.conf 
<VirtualHost *:80>
    ServerName www1.danran.com
    DocumentRoot "/data/web/vhost1"
    <Directory "/data/web/vhost1">
            Options FollowSymLinks
            AllowOverride None
            Require all granted
    </Directory>
</VirtualHost>

编辑虚拟主机2的配置文件
[root@web2 ~]# vim  /etc/httpd/conf.d/vhost2.conf 
Listen 8080
<VirtualHost *:8080>
    ServerName www2.danran.com
    DocumentRoot "/data/web/vhost1"
    <Directory "/data/web/vhost1">
            Options FollowSymLinks
            AllowOverride None
            Require all granted
    </Directory>
</VirtualHost>

[root@web2 ~]# systemctl start httpd.service 

HAProxy

[root@HAProxy ~]# vim /etc/haproxy/haproxy.cfg
    frontend myweb *:80
        cookie WEBSRV indirect nocache
        acl static path_end .jpg .jpeg .png .gif .txt .html \定义ACL的组static以.jpg .jpeg .png .gif .txt .html结尾的文件
        use_backend staticsrvs  if static  \当符合条件时使用static主机组
        default_backend dynsrvs  \当不符合use_bckend条件时使用默认default_backend主机组

        backend dynsrvs \定义动态主机组
            balance roundrobin
            server dynsrv1 172.16.253.105:80 check cookie dynsrv1
            server dynsrv2 172.16.253.105:8080 check cookie dynsrv2
        backend staticsrvs  \定义静态主机组
            balance roundrobin
            server staticsrv1 172.16.253.191:80 check
            server staticsrv2 172.16.253.191:8080 check
[root@HAProxy ~]# systemctl restart haproxy

client

[root@client ~]# curl http://172.16.253.108/index.html
<h1> Image Server 1 </h1>
[root@client ~]# curl http://172.16.253.108/index.html
<h1> image Server 2 </h1>
[root@client ~]# curl http://172.16.253.108/index.php
<h1> Application Server 2</h1>
[root@client ~]# curl http://172.16.253.108/index.php
<h1> Application Server 2</h1>

拒绝curl访问web

HAProxy

[root@HAProxy ~]# vim /etc/haproxy/haproxy.cfg
    frontend myweb *:80
        cookie WEBSRV indirect nocache
        acl static path_end .jpg .jpeg .png .gif .txt .html \定义ACL的组static以.jpg .jpeg .png .gif .txt .html结尾的文件
        use_backend staticsrvs  if static  \当符合条件时使用static主机组
        default_backend dynsrvs  \当不符合use_bckend条件时使用默认default_backend主机组
        acl bad_browsers hdr_reg(User-Agent) .*curl.* \定义请求报文中包含curl的ACL组为bad_browsers
        block if bad_browsers \阻塞bad_browsers组的访问

        backend dynsrvs \定义动态主机组
            balance roundrobin
            server dynsrv1 172.16.253.105:80 check cookie dynsrv1
            server dynsrv2 172.16.253.105:8080 check cookie dynsrv2
        backend staticsrvs  \定义静态主机组
            balance roundrobin
            server staticsrv1 172.16.253.191:80 check
            server staticsrv2 172.16.253.191:8080 check
[root@HAProxy ~]# systemctl restart haproxy

client

[root@client ~]# curl http://172.16.253.108/index.html
<html><body><h1>403 Forbidden</h1>
Request forbidden by administrative rules.
</body></html>

定义仅允许danran.com域内的的主机访问

HAProxy

[root@HAProxy ~]# vim /etc/haproxy/haproxy.cfg
    frontend myweb *:80
        cookie WEBSRV indirect nocache
        acl static path_end .jpg .jpeg .png .gif .txt .html \定义ACL的组static以.jpg .jpeg .png .gif .txt .html结尾的文件
        use_backend staticsrvs  if static  \当符合条件时使用static主机组
        default_backend dynsrvs  \当不符合use_bckend条件时使用默认default_backend主机组
        acl valid_referers hdr_reg(Referer) .danran.com
        block unless valid_referers \阻塞除了valid_referers组之外的所有人的访问

        backend dynsrvs \定义动态主机组
            balance roundrobin
            server dynsrv1 172.16.253.105:80 check cookie dynsrv1
            server dynsrv2 172.16.253.105:8080 check cookie dynsrv2
        backend staticsrvs  \定义静态主机组
            balance roundrobin
            server staticsrv1 172.16.253.191:80 check
            server staticsrv2 172.16.253.191:8080 check
[root@HAProxy ~]# systemctl restart haproxy

client

模拟www.danran.com主机访问
[root@client ~]# curl -e "http://www.danran.com/index.php" http://172.16.253.108/index.php 
<h1> Application Server 2</h1>

Flask-Login 使用和进阶

在我们使用 Flask 构建一个系统时,用户登录注册是一个必不可少的过程,我们可以自己实现一个登录的功能,但是由于涉及的面很多,所以我们更多情况下还是寻求已有的模块来进行使用。

在 Flask 中比较常用的就是 Flask-Login 了,这里就以 Flask-Login 为例,给大家介绍一下如何使用Flask-Login 进行登录注销,以及帮助大家解答一些可能比较常见的问题。

使用入门

首先,我们先概述一下我们这里的例子,我们这个例子有三个 url,分别是:

/auth/login     用于登录
/auth/logout    用于注销
/test               用于测试,需要登录才能访问

好,这就是我们的前提概述了,下面我们就开始介绍。

安装必要的库

毫无疑问,我们要使用 Flask-Login ,那就必须安装它,安装使用 pip 还是很简单的,此外,因为我们登录涉及到登录表单,所以,还需要安装 wtform ,所以最后总共需要安装的有:

pip install Flask==0.10.1
pip install Flask-Login==0.3.2
pip install Flask-WTF==0.12
pip install WTForms==2.1

编写 web 框架

首先,在开始登录之前,我们先把整个 web 的框架搭建出来,也就是,我们要能够先在不登录的情况下访问到上面提到的三个url,这个架构比较简单了,我就直接放在一个叫做 app.py 的文件中了。

#!/usr/bin/env python
# encoding: utf-8
from flask import Flask, Blueprint

app = Flask(__name__)

# url redirect
auth = Blueprint('auth', __name__)

@auth.route('/login', methods=['GET', 'POST'])
def login():
    return "login page"

@auth.route('/logout', methods=['GET', 'POST'])
def logout():
    return "logout page"    

# test method
@app.route('/test')
def test():
    return "yes , you are allowed"

app.register_blueprint(auth, url_prefix='/auth')
app.run(debug=True)

现在,我们可以尝试一下运行一下这个框架,使用

python app.py

运行即可,然后打开浏览器,分别访问一下:

http://localhost:5000/test
http://localhost:5000/auth/login
http://localhost:5000/auth/logout

看一下是否都正常。

设置登录才能查看

现在框架已经设置完毕,那么我们就可以尝试一下设置登录需求的,也就是说我们将 test 和 auth/logout 这两个 page 设置成登录之后才能查看。因为这个功能已经和 login 有关系了,所以这时我们就需要使用到 Flask-Login 了。

我们可以这样来改变代码:

#!/usr/bin/env python
# encoding: utf-8
from flask import Flask, Blueprint
from flask.ext.login import LoginManager, login_required

app = Flask(__name__)

# 以下这段是新增加的============
app.secret_key = 's3cr3t'
login_manager = LoginManager()
login_manager.session_protection = 'strong'
login_manager.login_view = 'auth.login'
login_manager.init_app(app)

@login_manager.user_loader
def load_user(user_id):
    return None
# 以上这段是新增加的============

auth = Blueprint('auth', __name__)

@auth.route('/login', methods=['GET', 'POST'])
def login():
    return "login page"

@auth.route('/logout', methods=['GET', 'POST'])
@login_required
def logout():
    return "logout page"

# test method
@app.route('/test')
@login_required
def test():
    return "yes , you are allowed"

app.register_blueprint(auth, url_prefix='/auth')
app.run(debug=True)

其实我们就增加了两项代码,一项是初始化 LoginManager 的,另外一项就是给 test 和 auth.logout 添加了 login_required 的装饰器,表示要登录了才能访问。

你也许会有疑问:@login_manager.user_loader 这个装饰器是干嘛用的。这个在后面的 Question 中有详细得介绍,在这里我们只需要知道这个函数需要返回指定 id 的用户,如果没有就返回 None。这里因为设置框架所以就默认返回 None。

用户授权

到此,我们发现访问 test 是不能访问的,会被重定向到 login 的那个 page。那我们看一下我们现在的代码,我们发现 login_required 有了,那么就差 login 了,好,接下来就写 login,所以我们就来看看 Flask-Login 的文档,找找,我们会发现一个叫做:login_user 的函数,看看它的原型:

flask.ext.login.login_user(user, remember=False, force=False, fresh=True)

这里需要一个 user 的对象,所以我们就先构建一个 Model,其实,这个 Model 还是有一点讲究的,所以我们最好是继承自 Flask-Login 的 UserMixin , 然后需要实现几个方法, Model 为:

# user models
    class User(UserMixin):
        def is_authenticated(self):
            return True

        def is_actice(self):
            return True

        def is_anonymous(self):
            return False

        def get_id(self):
            return "1"

这里给所有的函数都返回了默认值,默认对应的情况是这个用户已经登录,并且是有效的。

然后在 login 的 view 里面 login_user,logout 的 view 里面 logout_user,这样整个登录过程就连接起来了,最后的代码是这样的:

#!/usr/bin/env python
    # encoding: utf-8
    from flask import Flask, Blueprint
    from flask.ext.login import (LoginManager, login_required, login_user,
                                 logout_user, UserMixin)

    app = Flask(__name__)


    # user models
    class User(UserMixin):
        def is_authenticated(self):
            return True

        def is_actice(self):
            return True

        def is_anonymous(self):
            return False

        def get_id(self):
            return "1"

    # flask-login
    app.secret_key = 's3cr3t'
    login_manager = LoginManager()
    login_manager.session_protection = 'strong'
    login_manager.login_view = 'auth.login'
    login_manager.init_app(app)

    @login_manager.user_loader
    def load_user(user_id):
        user = User()
        return user

    auth = Blueprint('auth', __name__)

    @auth.route('/login', methods=['GET', 'POST'])
    def login():
        user = User()
        login_user(user)
        return "login page"

    @auth.route('/logout', methods=['GET', 'POST'])
    @login_required
    def logout():
        logout_user()
        return "logout page"

    # test method
    @app.route('/test')
    @login_required
    def test():
        return "yes , you are allowed"

    app.register_blueprint(auth, url_prefix='/auth')
    app.run(debug=True)

Summary

到此,这就是一个比较精简的 Flask-Login 教程了,通过这个框架大家可以自行扩展,达到更丰富的功能,后续会连续这个 Login 功能继续讲解一下权限控制。

Question

未登录访问鉴权页面如何处理?

如果未登录访问了一个作了 login_required 限制的 view,那么 Flask-Login 会默认 flash一条消息,并且将重定向到 log in view,如果你没有指定 log in view,那么 Flask-Login 将会抛出一个 401 错误。

如何指定 log in view

指定 log in view 只需要直接设置 login_manager 即可:

login_manager.login_view = "auth.login"

如何自定义 flash 消息

如果需要自定义 flash 的消息,那么还是简单设置 login_manager,

login_manager.login_message = u"请登录!"

还可以设置 flash 消息的级别,一般设置成 info 或者 error:

login_manager.login_message_category = "info"

自定义未登录处理函数

如果你不想使用默认的规则,那么你也可以自定义未登录情况的处理函数,只需要使用 login_manager 的 unauthorized_handler 装饰器即可。

@login_manager.unauthorized_handler
    def unauthorized():
        # do stuff
        return render_template("some template")

匿名用户是怎么处理的?有哪些属性?

在 Flask-Login 中,如果一个匿名用户访问站点,那么 current_user 对象会被设置成一个 AnonymousUserMixin 的对象,AnonymousUserMixin 对象有以下方法和属性:

  • is_active and is_authenticated are False
  • is_anonymous is True
  • get_id() returns None

自定义匿名用户 Model

如果你有需求自定义匿名用户的 Model,那么你可以通过设置 login_manager 的 anonymous_user 属性来实现,而赋值的对象只需是可调用对象(class 和 function都行)即可。

login_manager.anonymous_user = MyAnonymousUser

Flask-Login 如何加载用户的

当一个请求过来的时候,如果 ctx.user 没有值,那么 Flask-Login 就会使用 session 中 session[‘user_id’] 作为参数,调用 login_manager 中使用 user_loader 装饰器设置的 callback 函数加载用户,需要注意的是,如果指定的 user_id 无效,不应该抛出异常,而是应该返回 None。

@login_manager.user_loader
    def load_user(user_id):
        return User.get(user_id)

session[‘user_id’] 其实是在调用 login_in 函数之后自动设置的。

如何控制 Flask-Login 的 session 过期时间

在 Flask-Login 中,如果你不特殊处理的话,session 是在你关闭浏览器之后就失效的。也就是说每次重新打开页面都是需要重新登录的。

如果你需要自己控制 session 的过期时间的话,

  1. 首先需要设置 login_manager 的 session类型为永久的,
  2. 然后再设置 session 的过期时间
session.permanent = True
app.permanent_session_lifetime = timedelta(minutes=5)

同时,还需要注意的是 cookie 的默认有效期其实是 一年 的,所以,我们最好也设置一下:

login_manager.remember_cookie_duration=timedelta(days=1)

如何在同域名下的多个系统共享登录状态

这个需求可能在公司里面会比较常见,也就是说我们一个公司域名下面会有好多个子系统,但是这些子系统都是不同部门开发的,那么,我们如何在这不同系统间共享登录状态?也就是说,只要在某一个系统登录了,在使用其他系统的时候也共享着登录的状态,不需要再次登录,除非登录失效。

Docker Swarm + HAProxy 实现高可用

什么是高可用性?

  • 高可用性: High Availability (简称 HA)
  • 可用性 (Availability) = 可靠性 (Reliability) + 可维护性 (Maintainability)
  • 自动检测、自动切换、自动恢复
  • 主从方式、互备方式、集群方式

集群的三大核心概念

  • 集群 (Cluster)
  • 节点 (Node)
  • 服务 (Service)

本文将介绍 Docker Swarm + HAProxy 来实现服务的高可用性

环境信息

  • 本机 Mac
  • Docker Version 17.06.1-ce-mac24 (18950)

创建节点(虚拟机)

未分类

在 Mac 执行如下命令创建节点

$ docker-machine create manager1
$ docker-machine create work1
$ docker-machine create work2
$ docker-machine create work3

未分类

进入节点(虚拟机)

再开启另外的4个窗口,分别执行

$ docker-machine ssh manager1
$ docker-machine ssh work1
$ docker-machine ssh work2
$ docker-machine ssh work3

未分类

初始化集群

1、初始化 Manager

未分类

在 manager1 节点上执行

docker swarm init --advertise-addr 192.168.99.100`

未分类

命令成功后,会有提示,如何将 worker 加入集群,即 docker swarm … 这行命令。

提示:如果忘记此处的 token,可以使用 docker swarm join-token worker 命令查看。

2、将 work1,work2,work3 加入集群

未分类

在 work1, work2, work3 上分别执行,此行命令详见上一步中的提示

docker swarm join --token SWMTKN-1-1rt31l3671hig69vkfqha994rydnwr67gxt6o0suv71zzobpgn-b930rnc1pzewxv9h73plq58rd 192.168.99.100:2377

未分类

笔记:集群中加入worker的命令是

docker swarm join --token [token] [manager-ip]:[manager-port]

创建服务

此处以 nginx 为例。首先,创建有 2 个副本的 nginx 服务,在 manger1 上执行

$ docker service create --replicas 2 
-d 
-p 8080:80 
--name any-nginx 
registry.docker-cn.com/library/nginx

提示:此处用到了镜像加速仓库

执行完毕之后,查看服务列表信息

$ docker service ls

未分类

查看服务信息

$ docker service ps vv

笔记:查看服务信息命令 docker service ps [服务ID]

未分类

从服务信息可以看到 nginx 已运行在 manager1 和 work1 上

未分类

分别访问 http://192.168.99.100:8080 和 http://192.168.99.101:8080 ,会得到如下结果:

未分类

分别在 manager1 和 work1 上查看容器运行情况,结果如下:

未分类

服务的扩容与缩容

将 any-nginx 扩容为 3 份,在 manager1 上执行

$ docker service scale any-nginx=3

再次查看服务信息,可看到 any-nginx.3 在 work2 上运行

未分类

未分类

服务高可用测试

目前,有3个运行的容器保证服务的可用性,如果其中一个容器意外关闭,会发生什么情况呢?

将 work2 上正在运行的容器关闭,在 work2 上执行:

未分类

再次查看服务信息,可以看到 any-nginx.3 在 work2 上自动重启了

未分类

另外一个问题,如果 work2 宕机了,会发生什么情况?

在 Mac 上关闭 work2 节点:

$ docker-machine stop work2

未分类

未分类

不难看出 work2 节点关闭之后,work3 节点自动启动了 any-nginx.3,保证了整个集群运行的还是 3 个副本。

补充:若work1,work2,work3全部关闭,manager1 上将运行 3 个 any-nginx 容器。此处不演示。

总结:在内存允许的条件下,docker swarm 集群会稳定运行指定数量的容器,具有自我修复的能力,以此来保证服务的高可用性!

使用 HAProxy 来添加外部负载均衡

未分类

此处 HAProxy 将运行于本机,以下命令均在本机操作。

1、创建 HAProxy 配置文件 haproxy.cfg

$ vim haproxy.cfg
global
    daemon
    maxconn 25600
defaults
    mode http
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms
frontend http-in
    bind *:80
    default_backend servers
backend servers
    server server1 192.168.99.100:8080 maxconn 32 check
    server server2 192.168.99.101:8080 maxconn 32 check
    server server3 192.168.99.102:8080 maxconn 32 check
    server server4 192.168.99.103:8080 maxconn 32 check

2、创建 Dockerfile 自定义镜像

$ vim Dockerfile
FROM haproxy
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg

3、制作镜像

$ docker build -t any-haproxy .

4、启动 HAProxy

$ docker run -d -p 8080:80 any-haproxy

未分类

5、测试,访问 http://127.0.0.1:8080/

未分类

Centos下安装 PHP7.0.2

PHP7 已经出来挺长一段时间了,不过还一直使用较低版本的 PHP。最近有一台闲置的 VPS,准备用来建一个简单的动态页面,便想尝试下 PHP7 的一些新特性。以下简单的记录下安装步骤。

一、安装前准备(安装编译工具及库文件)

yum install make apr* autoconf automake curl-devel gcc gcc-c++ gtk+-devel zlib-devel openssl openssl-devel pcre-devel gd gettext gettext-devel kernel keyutils patch perl kernel-headers compat* mpfr cpp glibc libgomp libstdc++-devel ppl cloog-ppl keyutils-libs-devel libcom_err-devel libsepol-devel libselinux-devel krb5-devel  libXpm* freetype freetype-devel freetype* fontconfig fontconfig-devel libjpeg* libpng* php-common php-gd ncurses* libtool* libxml2 libxml2-devel patch

安装前说明 : PHP7安装包存放路径 /usr/local/src , PHP7安装目录 /usr/local/php7

二、下载 PHP7

wget http://cn2.php.net/distributions/php-7.0.2.tar.gz

解压&进入目录

tar -zxvf php-7.0.2.tar.gz

cd php-7.0.2

三、生成 Makefile

./configure --prefix=/usr/local/php7 --enable-fpm --with-fpm-user=nginx --with-fpm-group=nginx --with-mysqli --with-zlib --with-curl --with-gd --with-jpeg-dir --with-png-dir --with-freetype-dir --with-openssl --enable-mbstring --enable-xml --enable-session --enable-ftp --enable-pdo -enable-tokenizer --enable-zip

四、编译

make

如果上面 make 出现如下错误:

cc: Internal error: Killed (program cc1)

Please submit a full bug report.

See <http://bugzilla.redhat.com/bugzilla> for instructions.

make: *** [ext/fileinfo/libmagic/apprentice.lo] Error 1

原因是内存不够,一般内存小的 VPS 可能碰到该问题,内存足够一般不会碰到该问题。解决方法是在 configure 时加上 –disable-fileinfo 参数(disable前面是两个 – ), 你需要重新执行 configure 操作,然后再进行 make 编译 。

如果 make 出现下面这中错误:

collect2: ld returned 1 exit status

make: *** [sapi/cli/php] Error 1

解决办法:

make ZEND_EXTRA_LIBS='-liconv'

ln -s /usr/local/lib/libiconv.so.2   /usr/lib64/

五、安装

make install

六、操作配置文件

cp /usr/local/src/php-7.0.2/php.ini-development  /usr/local/php7/lib/php.ini

cp /usr/local/php7/etc/php-fpm.conf.default /usr/local/php7/etc/php-fpm.conf

cp /usr/local/php7/etc/php-fpm.d/www.conf.default /usr/local/php7/etc/php-fpm.d/www.conf

cp /usr/local/src/php-7.0.2/sapi/fpm/init.d.php-fpm /etc/init.d/php-fpm

chmod +x /etc/init.d/php-fpm

七、启动 php

/etc/init.d/php-fpm start

Centos7部署Kubernetes集群

一、环境介绍及准备:

1、物理机操作系统

物理机操作系统采用Centos7.3 64位,细节如下。

[root@localhost ~]# uname -a
Linux localhost.localdomain 3.10.0-514.6.1.el7.x86_64 #1 SMP Wed Jan 18 13:06:36 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
[root@localhost ~]# cat /etc/redhat-release 
CentOS Linux release 7.3.1611 (Core)

2、主机信息

本文准备了三台机器用于部署k8s的运行环境,细节如下:

未分类

设置三台机器的主机名:

Master上执行:

[root@localhost ~]#  hostnamectl --static set-hostname  k8s-master

Node1上执行:

[root@localhost ~]# hostnamectl --static set-hostname  k8s-node-1

Node2上执行:

[root@localhost ~]# hostnamectl --static set-hostname  k8s-node-2

在三台机器上设置hosts,均执行如下命令:

echo '10.0.251.148    k8s-master
10.0.251.148   etcd
10.0.251.148   registry
10.0.251.153   k8s-node-1
10.0.251.155    k8s-node-2' >> /etc/hosts

3、关闭三台机器上的防火墙

systemctl disable firewalld.service
systemctl stop firewalld.service

二、部署etcd

k8s运行依赖etcd,需要先部署etcd,本文采用yum方式安装:

[root@localhost ~]# yum install etcd -y

未分类

yum安装的etcd默认配置文件在/etc/etcd/etcd.conf。编辑配置文件,更改以下带颜色部分信息:

[root@localhost ~]# vi /etc/etcd/etcd.conf

# [member]
ETCD_NAME=master
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
#ETCD_WAL_DIR=""
#ETCD_SNAPSHOT_COUNT="10000"
#ETCD_HEARTBEAT_INTERVAL="100"
#ETCD_ELECTION_TIMEOUT="1000"
#ETCD_LISTEN_PEER_URLS="http://0.0.0.0:2380"
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379,http://0.0.0.0:4001"
#ETCD_MAX_SNAPSHOTS="5"
#ETCD_MAX_WALS="5"
#ETCD_CORS=""
#
#[cluster]
#ETCD_INITIAL_ADVERTISE_PEER_URLS="http://localhost:2380"
# if you use different ETCD_NAME (e.g. test), set ETCD_INITIAL_CLUSTER value for this name, i.e. "test=http://..."
#ETCD_INITIAL_CLUSTER="default=http://localhost:2380"
#ETCD_INITIAL_CLUSTER_STATE="new"
#ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_ADVERTISE_CLIENT_URLS="http://etcd:2379,http://etcd:4001"
#ETCD_DISCOVERY=""
#ETCD_DISCOVERY_SRV=""
#ETCD_DISCOVERY_FALLBACK="proxy"
#ETCD_DISCOVERY_PROXY=""

启动并验证状态

[root@localhost ~]# systemctl start etcd
[root@localhost ~]#  etcdctl set testdir/testkey0 0
0
[root@localhost ~]#  etcdctl get testdir/testkey0 
0
[root@localhost ~]# etcdctl -C http://etcd:4001 cluster-health
member 8e9e05c52164694d is healthy: got healthy result from http://0.0.0.0:2379
cluster is healthy
[root@localhost ~]# etcdctl -C http://etcd:2379 cluster-health
member 8e9e05c52164694d is healthy: got healthy result from http://0.0.0.0:2379
cluster is healthy

扩展:Etcd集群部署参见——http://www.cnblogs.com/zhenyuyaodidiao/p/6237019.html

三、部署master

1、安装Docker

[root@k8s-master ~]# yum install docker

未分类

配置Docker配置文件,使其允许从registry中拉取镜像。

[root@k8s-master ~]# vim /etc/sysconfig/docker

# /etc/sysconfig/docker

# Modify these options if you want to change the way the docker daemon runs
OPTIONS='--selinux-enabled --log-driver=journald --signature-verification=false'
if [ -z "${DOCKER_CERT_PATH}" ]; then
    DOCKER_CERT_PATH=/etc/docker
fi
OPTIONS='--insecure-registry registry:5000'

设置开机自启动并开启服务

[root@k8s-master ~]# chkconfig docker on
[root@k8s-master ~]# service docker start

2、安装kubernets

[root@k8s-master ~]# yum install kubernetes

未分类

3、配置并启动kubernetes

在kubernetes master上需要运行以下组件:

  • Kubernets API Server

  • Kubernets Controller Manager

  • Kubernets Scheduler

相应的要更改以下几个配置中带颜色部分信息:

3.1 /etc/kubernetes/apiserver

[root@k8s-master ~]# vim /etc/kubernetes/apiserver

###
# kubernetes system config
#
# The following values are used to configure the kube-apiserver
#

# The address on the local server to listen to.
KUBE_API_ADDRESS="--insecure-bind-address=0.0.0.0"

# The port on the local server to listen on.
KUBE_API_PORT="--port=8080"

# Port minions listen on
# KUBELET_PORT="--kubelet-port=10250"

# Comma separated list of nodes in the etcd cluster
KUBE_ETCD_SERVERS="--etcd-servers=http://etcd:2379"

# Address range to use for services
KUBE_SERVICE_ADDRESSES="--service-cluster-ip-range=10.254.0.0/16"

# default admission control policies
#KUBE_ADMISSION_CONTROL="--admission-control=NamespaceLifecycle,NamespaceExists,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota"
KUBE_ADMISSION_CONTROL="--admission-control=NamespaceLifecycle,NamespaceExists,LimitRanger,SecurityContextDeny,ResourceQuota"

# Add your own!
KUBE_API_ARGS=""

3.2 /etc/kubernetes/config

[root@k8s-master ~]# vim /etc/kubernetes/config

###
# kubernetes system config
#
# The following values are used to configure various aspects of all
# kubernetes services, including
#
#   kube-apiserver.service
#   kube-controller-manager.service
#   kube-scheduler.service
#   kubelet.service
#   kube-proxy.service
# logging to stderr means we get it in the systemd journal
KUBE_LOGTOSTDERR="--logtostderr=true"

# journal message level, 0 is debug
KUBE_LOG_LEVEL="--v=0"

# Should this cluster be allowed to run privileged docker containers
KUBE_ALLOW_PRIV="--allow-privileged=false"

# How the controller-manager, scheduler, and proxy find the apiserver
KUBE_MASTER="--master=http://k8s-master:8080"

启动服务并设置开机自启动

[root@k8s-master ~]# systemctl enable kube-apiserver.service
[root@k8s-master ~]# systemctl start kube-apiserver.service
[root@k8s-master ~]# systemctl enable kube-controller-manager.service
[root@k8s-master ~]# systemctl start kube-controller-manager.service
[root@k8s-master ~]# systemctl enable kube-scheduler.service
[root@k8s-master ~]# systemctl start kube-scheduler.service

四、部署node

1、安装docker

参见3.1

2、安装kubernets

参见3.2

3、配置并启动kubernetes

在kubernetes node上需要运行以下组件:

  • Kubelet

  • Kubernets Proxy

相应的要更改以下几个配置文中带颜色部分信息:

3.1 /etc/kubernetes/config

[root@K8s-node-1 ~]# vim /etc/kubernetes/config

###
# kubernetes system config
#
# The following values are used to configure various aspects of all
# kubernetes services, including
#
#   kube-apiserver.service
#   kube-controller-manager.service
#   kube-scheduler.service
#   kubelet.service
#   kube-proxy.service
# logging to stderr means we get it in the systemd journal
KUBE_LOGTOSTDERR="--logtostderr=true"

# journal message level, 0 is debug
KUBE_LOG_LEVEL="--v=0"

# Should this cluster be allowed to run privileged docker containers
KUBE_ALLOW_PRIV="--allow-privileged=false"

# How the controller-manager, scheduler, and proxy find the apiserver
KUBE_MASTER="--master=http://k8s-master:8080"

3.2 /etc/kubernetes/kubelet

[root@K8s-node-1 ~]# vim /etc/kubernetes/kubelet

###
# kubernetes kubelet (minion) config

# The address for the info server to serve on (set to 0.0.0.0 or "" for all interfaces)
KUBELET_ADDRESS="--address=0.0.0.0"

# The port for the info server to serve on
# KUBELET_PORT="--port=10250"

# You may leave this blank to use the actual hostname
KUBELET_HOSTNAME="--hostname-override=k8s-node-1"

# location of the api-server
KUBELET_API_SERVER="--api-servers=http://k8s-master:8080"

# pod infrastructure container
KUBELET_POD_INFRA_CONTAINER="--pod-infra-container-image=registry.access.redhat.com/rhel7/pod-infrastructure:latest"

# Add your own!
KUBELET_ARGS=""

启动服务并设置开机自启动

[root@k8s-master ~]# systemctl enable kubelet.service
[root@k8s-master ~]# systemctl start kubelet.service
[root@k8s-master ~]# systemctl enable kube-proxy.service
[root@k8s-master ~]# systemctl start kube-proxy.service

4、查看状态

在master上查看集群中节点及节点状态

[root@k8s-master ~]#  kubectl -s http://k8s-master:8080 get node
NAME         STATUS    AGE
k8s-node-1   Ready     3m
k8s-node-2   Ready     16s
[root@k8s-master ~]# kubectl get nodes
NAME         STATUS    AGE
k8s-node-1   Ready     3m
k8s-node-2   Ready     43s

至此,已经搭建了一个kubernetes集群,但目前该集群还不能很好的工作,请继续后续的步骤。

五、创建覆盖网络——Flannel

1、安装Flannel

在master、node上均执行如下命令,进行安装

[root@k8s-master ~]# yum install flannel

未分类

版本为0.0.5

2、配置Flannel

master、node上均编辑/etc/sysconfig/flanneld,修改红色部分

[root@k8s-master ~]# vi /etc/sysconfig/flanneld

# Flanneld configuration options

# etcd url location.  Point this to the server where etcd runs
FLANNEL_ETCD_ENDPOINTS="http://etcd:2379"

# etcd config key.  This is the configuration key that flannel queries
# For address range assignment
FLANNEL_ETCD_PREFIX="/atomic.io/network"

# Any additional options that you want to pass
#FLANNEL_OPTIONS=""

3、配置etcd中关于flannel的key

Flannel使用Etcd进行配置,来保证多个Flannel实例之间的配置一致性,所以需要在etcd上进行如下配置:(‘/atomic.io/network/config’这个key与上文/etc/sysconfig/flannel中的配置项FLANNEL_ETCD_PREFIX是相对应的,错误的话启动就会出错)

[root@k8s-master ~]# etcdctl mk /atomic.io/network/config '{ "Network": "10.0.0.0/16" }'
{ "Network": "10.0.0.0/16" }

4、启动

启动Flannel之后,需要依次重启docker、kubernete。

在master执行:

systemctl enable flanneld.service 
systemctl start flanneld.service 
service docker restart
systemctl restart kube-apiserver.service
systemctl restart kube-controller-manager.service
systemctl restart kube-scheduler.service

在node上执行:

systemctl enable flanneld.service 
systemctl start flanneld.service 
service docker restart
systemctl restart kubelet.service
systemctl restart kube-proxy.service

awk 系列:如何使用 awk 的特殊模式 BEGIN 和 END

在 awk 系列的第八节,我们介绍了一些强大的 awk 命令功能,它们是变量、数字表达式和赋值运算符。

本节我们将学习更多的 awk 功能,即 awk 的特殊模式:BEGIN 和 END。

学习 awk 的模式 BEGIN 和 END

随着我们逐渐展开,并探索出更多构建复杂 awk 操作的方法,将会证明 awk 的这些特殊功能的是多么强大。

开始前,先让我们回顾一下 awk 系列的介绍,记得当我们开始这个系列时,我就指出 awk 指令的通用语法是这样的:

# awk 'script' filenames  

在上述语法中,awk 脚本拥有这样的形式:

/pattern/ { actions } 

你通常会发现脚本中的模式(/pattern/)是一个正则表达式,此外,你也可以在这里用特殊模式 BEGIN 和 END。因此,我们也能按照下面的形式编写一条 awk 命令:

awk '
BEGIN { actions } 
/pattern/ { actions }
/pattern/ { actions }
……….
END { actions } 
' filenames

假如你在 awk 脚本中使用了特殊模式:BEGIN 和 END,以下则是它们对应的含义:

  • BEGIN 模式:是指 awk 将在读取任何输入行之前立即执行 BEGIN 中指定的动作。
  • END 模式:是指 awk 将在它正式退出前执行 END 中指定的动作。

含有这些特殊模式的 awk 命令脚本的执行流程如下:

  1. 当在脚本中使用了 BEGIN 模式,则 BEGIN 中所有的动作都会在读取任何输入行之前执行。
  2. 然后,读入一个输入行并解析成不同的段。
  3. 接下来,每一条指定的非特殊模式都会和输入行进行比较匹配,当匹配成功后,就会执行模式对应的动作。对所有你指定的模式重复此执行该步骤。
  4. 再接下来,对于所有输入行重复执行步骤 2 和 步骤 3。
  5. 当读取并处理完所有输入行后,假如你指定了 END 模式,那么将会执行相应的动作。

当你使用特殊模式时,想要在 awk 操作中获得最好的结果,你应当记住上面的执行顺序。

为了便于理解,让我们使用第八节的例子进行演示,那个例子是关于 Tecmint 拥有的域名列表,并保存在一个叫做 domains.txt 的文件中。

news.tecmint.com
tecmint.com
linuxsay.com
windows.tecmint.com
tecmint.com
news.tecmint.com
tecmint.com
linuxsay.com
tecmint.com
news.tecmint.com
tecmint.com
linuxsay.com
windows.tecmint.com
tecmint.com
$ cat ~/domains.txt

未分类

在这个例子中,我们希望统计出 domains.txt 文件中域名 tecmint.com 出现的次数。所以,我们编写了一个简单的 shell 脚本帮助我们完成任务,它使用了变量、数学表达式和赋值运算符的思想,脚本内容如下:

#!/bin/bash
for file in $@; do
if [ -f $file ] ; then
### 输出文件名
echo "File is: $file"
### 输出一个递增的数字记录包含 tecmint.com 的行数
awk '/^tecmint.com/ { counter+=1 ; printf "%sn", counter ; }' $file
else
### 若输入不是文件,则输出错误信息
echo "$file 不是一个文件,请指定一个文件。" >&2 && exit 1
fi
done
### 成功执行后使用退出代码 0 终止脚本
exit 0

现在让我们像下面这样在上述脚本的 awk 命令中应用这两个特殊模式:BEGIN 和 END:

我们应当把脚本:

awk '/^tecmint.com/ { counter+=1 ; printf "%sn", counter ; }' $file

改成:

awk ' BEGIN {  print "文件中出现 tecmint.com 的次数是:" ; }
/^tecmint.com/ {  counter+=1  ;  }
END {  printf "%sn",  counter  ; } 
'  $file

在修改了 awk 命令之后,现在完整的 shell 脚本就像下面这样:

#!/bin/bash
for file in $@; do
if [ -f $file ] ; then
### 输出文件名
echo "File is: $file"
### 输出文件中 tecmint.com 出现的总次数
awk ' BEGIN {  print "文件中出现 tecmint.com 的次数是:" ; }
/^tecmint.com/ {  counter+=1  ;  }
END {  printf "%sn",  counter  ; } 
'  $file
else
### 若输入不是文件,则输出错误信息
echo "$file 不是一个文件,请指定一个文件。" >&2 && exit 1
fi
done
### 成功执行后使用退出代码 0 终止脚本
exit 0

未分类

当我们运行上面的脚本时,它会首先输出 domains.txt 文件的位置,然后执行 awk 命令脚本,该命令脚本中的特殊模式 BEGIN 将会在从文件读取任何行之前帮助我们输出这样的消息“文件中出现 tecmint.com 的次数是:”。

接下来,我们的模式 /^tecmint.com/ 会在每个输入行中进行比较,对应的动作 { counter+=1 ; } 会在每个匹配成功的行上执行,它会统计出 tecmint.com 在文件中出现的次数。

最终,END 模式将会输出域名 tecmint.com 在文件中出现的总次数。

$ ./script.sh ~/domains.txt 

未分类

最后总结一下,我们在本节中演示了更多的 awk 功能,并学习了特殊模式 BEGIN 和 END 的概念。

CentOS 6.x 上搭建docker

Docker是一个能够把开发应用程序自动部署到容器的开源引擎。它由Docker公司的团队编写,基于Apache 2.0开源协议授权。它提供了一个简单、轻量的建模方式,使开发生命周期更高效快速,鼓励了面向服务的架构设计。

一、前提条件

1、内核

Docker 运行环境对内核要求比较高,一般建议直接在Ubuntu这样的平台上运行。但作为一个容器标准,Docker也支持其他如 CentOS,Mac OS X,Windows等其他平台。
目前Docker支持以下Red Hat和Red Hat系发行版:(不支持32位)

  • (RHEL|CentOS) 6.5及以上版本(64位)
  • Fedora 19及以上版本(64位)

在运行于这些系统时,需要 内核版本 >= 3.8 ,因为这些内核包含了运行Docker的一些特定修改。
查看内核版本:

# uname -r
2.6.32-431.el6.x86_64

注意:3.8 > 内核版本 >= 2.6.32-431 这些内核版本虽然也能支持Docker运行,但是支持得不好,仍会在运行时出现很多bug。推荐 内核版本 >= 3.8 。

2、检查Device Mapper

Docker默认使用AUFS作为存储驱动,但是AUFS并没有被包括在Linux的主线内核中。CentOS中可以使用Device Mapper作为存储驱动,这是在2.6.9内核版本引入的新功能。
需要先确认是否启用该功能:

# ls -l /sys/class/misc/device-mapper
lrwxrwxrwx 1 root root 0 8月  31 17:33 /sys/class/misc/device-mapper -> ../../devices/virtual/misc/device-mapper

如果没有检测到Device Mapper,需要安装其软件包:

# yum install device-mapper -y

然后重新加载 dm_mod 内核模块:

# modprobe dm_mod

二、升级内核版本

1、查看当前内核版本

# cat /etc/redhat-release
CentOS release 6.5 (Final)
# uname -r
2.6.32-431.el6.x86_64

CentOS 6.5 系统默认的内核版本是 2.6.32-431.el6.x86_64,不符合Docker的安装条件,需要升级内核版本。

2、升级内核版本至3.10.0

在yum的 ELRepo 源中,有 mainline(3.13.x)、long-term(3.10.x)这2个内核版本,考虑到long-term更稳定,会长期更新,所以选择这个版本。

导入 Public key

# rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org

安装ELRepo源

# rpm -ivh http://www.elrepo.org/elrepo-release-6-8.el6.elrepo.noarch.rpm

安装kernel-lt 或 kernel-ml(任选一个)

# kernel-lt,内核版本:3.10.x
# yum --enablerepo=elrepo-kernel install kernel-lt -y
# 或者kernel-ml,内核版本:3.13.x
# yum --enablerepo=elrepo-kernel install kernel-ml -y

编辑 grub.conf 文件,修改 Grub 引导顺序

# cat /etc/grub.conf
device (hd0) HD(1,800,64000,ab0fd74d-ecf9-42ff-8c69-0fa5d3af4fd2)
default=1
timeout=5
splashimage=(hd0,1)/grub/splash.xpm.gz
hiddenmenu
title CentOS (3.10.107-1.el6.elrepo.x86_64)
    root (hd0,1)
    kernel /vmlinuz-3.10.107-1.el6.elrepo.x86_64 ro root=/dev/mapper/vg_localhost-lv_root rd_NO_LUKS rd_NO_MD rd_LVM_LV=vg_localhost/lv_swap crashkernel=128M LANG=zh_CN.UTF-8 rd_LVM_LV=vg_localhost/lv_root  KEYBOARDTYPE=pc KEYTABLE=us rd_NO_DM rhgb quiet
    initrd /initramfs-3.10.107-1.el6.elrepo.x86_64.img
title CentOS (2.6.32-431.el6.x86_64)
    root (hd0,1)
    kernel /vmlinuz-2.6.32-431.el6.x86_64 ro root=/dev/mapper/vg_localhost-lv_root rd_NO_LUKS rd_NO_MD rd_LVM_LV=vg_localhost/lv_swap crashkernel=128M LANG=zh_CN.UTF-8 rd_LVM_LV=vg_localhost/lv_root  KEYBOARDTYPE=pc KEYTABLE=us rd_NO_DM rhgb quiet
    initrd /initramfs-2.6.32-431.el6.x86_64.img

确认刚安装的内核在哪个位置,然后设置default值(从0开始)。一般新安装的内核会在第一个位置,所以设置 default=0。

重启服务器,查看内核版本

# reboot
# uname -r
3.10.107-1.el6.elrepo.x86_64

三、安装Docker

对于CentOS 6.5,Docker可以在 EPEL 源中找到,安装 EPEL 源

# rpm -Uvh http://www.gtlib.gatech.edu/pub/fedora-epel/6/i386/epel-release-6-8.noarch.rpm

删除CentOS 6.5默认自带的Docker

# yum remove docker -y

安装docker-io的RPM包

# yum install docker-io -y

查看Docker版本

# docker version
Client version: 1.7.1
Client API version: 1.19
Go version (client): go1.4.2
Git commit (client): 786b29d/1.7.1
OS/Arch (client): linux/amd64
Server version: 1.7.1
Server API version: 1.19
Go version (server): go1.4.2
Git commit (server): 786b29d/1.7.1
OS/Arch (server): linux/amd64

启动Docker服务

# service docker start