CentOS从firewall切换为iptables防火墙

1、关闭firewall:

service firewalld stop
systemctl disable firewalld.service #禁止firewall开机启动

2、安装iptables防火墙

yum install iptables-services #安装

3、编辑iptables防火墙配置

vi /etc/sysconfig/iptables #编辑防火墙配置文件

下边是一个完整的配置文件:

Firewall configuration written by system-config-firewall

Manual customization of this file is not recommended.

*filter

:INPUT ACCEPT [0:0]

:FORWARD ACCEPT [0:0]

:OUTPUT ACCEPT [0:0]

-A INPUT -m state –state ESTABLISHED,RELATED -j ACCEPT

-A INPUT -p icmp -j ACCEPT

-A INPUT -i lo -j ACCEPT

-A INPUT -m state –state NEW -m tcp -p tcp –dport 22 -j ACCEPT

-A INPUT -m state –state NEW -m tcp -p tcp –dport 80 -j ACCEPT

-A INPUT -m state –state NEW -m tcp -p tcp –dport 3306 -j ACCEPT

-A INPUT -j REJECT –reject-with icmp-host-prohibited

-A FORWARD -j REJECT –reject-with icmp-host-prohibited

COMMIT

:wq! #保存退出

service iptables start #开启
systemctl enable iptables.service #设置防火墙开机启动

iptables之state扩展与开启被动模式ftp

1. state扩展

  该扩展至关重要,正是由于state才实现了连接追踪机制。
  连接追踪机制:每一台客户端与本地主机进行通讯时(有可能是通过本地主机去访问其他主机),本地主机都会在内存的一段空间中存储源IP、源端口、目标IP、目标端口的信息(每条记录都有生存时间)。当客户端下次再与本地主机进行通讯时,就可以通过查找之前存储的信息来追踪该客户端是否与本地主机通讯过。
  注意:开启连接追踪功能,需要占用内存的空间,影响系统性能。且前端负载均衡服务器若开启此功能,由于连接追踪模板空间有限制,空间满了之后,会导致后续的连接请求无法得到响应。

连接追踪机制已经追踪到并记录下来的连接保存在/proc/net/nf_conntrack文件中

ipv4     2 tcp      6 299 ESTABLISHED src=192.168.25.1 dst=192.168.25.128 sport=52832 dport=22 src=192.168.25.128 dst=192.168.25.1 sport=22 dport=52832 [ASSURED] mark=0 zone=0 use=2
以上内容的部分解释
ipv4:基于哪种底层协议
tcp:上层的协议
src sport:源地址 源端口
dst dport:目标地址,目标端口
ESTABLISHED:目前状态

连接追踪功能所能记录的最大连接数量通过该文件定义(可调整):

/proc/sys/net/nf_conntrack_max

调整连接追踪机制所能记录的最大连接数:

sysctl -w net.nf_conntrack_max=300000
echo 300000 > /proc/sys/net/nf_conntrack_max

conntrack所能追踪的连接数量最大值取决于/proc/sys/net/nf_conntrack_max的设定。已经追踪到并记录下来的连接位于/proc/net/nf_conntrack文件中,超时的连接将会被删除。当/proc/net/nf_conntrack满时,后续的新连接可能会超时。
解决办法(通常采用第一种方法):
  
1. 加大nf_conntrack_max的值
2. 降低nf_contrack条目的超时时长
  
不同协议的连接追踪时长记录在/proc/sys/net/netfilter/目录下的各文件中conntrack的子集,用于对报文的状态做连接追踪;

The "state" extension is a subset of the "conntrack" module. "state" allows access to the connection tracking state for this packet.

根据”连接追踪机制“去检查连接的状态;

  conntrack机制:追踪本机上的请求和响应之间的关系;状态有如下几种:
  NEW:新发出请求;连接追踪模板中不存在此连接的相关信息条目,因此,将其识别为第一次发出的请求;与协议无关。
  ESTABLISHED:NEW状态之后,连接追踪模板中为其建立的条目失效之前期间内所进行的通信状态;
   RELATED:相关联的连接;如ftp协议中的数据连接与命令连接之间的关系;
   INVALID:无法识别的连接;
   UNTRACKED:未进行追踪的连接;

例:

~] # iptables -A INPUT -s 172.18.0.0/16 -d 172.18.0.100 -p tcp -m multiport --dport 22,23,80 -m state --state NEW,ESTABLISHED -j ACCEPT
~] # iptables -A OUTPUT -d 172.18.0.0/16 -s 172.18.0.100 -p tcp -m multiport --sport 22,23,80 -m state --state ESTABLISHED -j ACCEPT
~] # iptables -A INPUT -d 172.18.100.6 -p icmp --icmp-type 8 -m state --state NEW,ESTABLISHED -j ACCEPT
~] # iptables -A OUTPUT -s 172.18.100.6 -p icmp --icmp-type 0 -m state --state ESTABLISHED -j ACCEPT

以上规则可合并为:

~] # iptables -A INPUT -m state --state ESTABLISHED -j ACCEPT
~] # iptables -A INPUT -d 172.18.100.6 -p tcp -m multiport --dports 22,23,80 -m state --state NEW -j ACCEPT
~] # iptables -A INPUT -d 172.18.100.6 -p icmp --icmp-type 8 -m state --state NEW -j ACCEPT
~] # iptables -A OUTPUT -m state --state ESTABLISHED -j ACCEPT

开启被动模式ftp

~] # modprobe nf_conntrack_ftp   #内核加载nf_conntrack_ftp模块
~] # systemctl start vsftpd.service     #开启vsftpd服务
~] # iptables -A INPUT-d 172.18.100.6 -m state --state ESTABLISTED,RELATED -j ACCEPT    
~] # iptables -A INPUT -d 172.18.100.6 -p tcp -m multiport --dports 21,22,23,80 -m state --state NEW  -j ACCEPT
~] # iptables -A OUTPUT -s 172.18.100.6 -m state --state ESTABLISHED -j ACCEPT

2. 如何开放被动模式的ftp服务?

(1) 装载ftp连接追踪的专用模块:

~]# modproble  nf_conntrack_ftp

(2) 放行命令连接(假设Server地址为172.16.100.67):

~]# iptables -A INPUT -d 172.16.100.67 -p tcp --dport 21 -m state --state NEW,ESTABLISHED -j ACCEPT
~]# iptables -A OUTPUT -s 172.16.100.67 -p tcp --sport 21 -m state --state ESTABLISHED -j ACCEPT

(3) 放行数据连接(假设Server地址为172.16.100.67):

~]# iptables -A INPUT -d 172.16.100.67 -p tcp -m state --state RELATED,ESTABLISHED -j ACCEPT
~]# iptables -I OUTPUT -s 172.16.100.67 -m state --state ESTABLISHED -j ACCEPT

3. 防火墙规则的检查次序

规则在链上的次序即为其检查时的生效次序,因此其优化使用有一定法则

  • 同类规则(访问同一应用),匹配范围小的放前面。用于特殊处理
  • 不同类的规则(访问不同应用),匹配范围大的放前面。
  • 应该将那些可由一条规则描述的多个规则合并为一条规则。(规则越少,检查的次数越少)。
  • 要设置默认策略

规则的有效期限
  iptables命令添加的规则,手动删除前,其生效期限为kernel的生命周期

4. 保存与加载预存的规则

保存规则:

CentOS 6:

#方式一:
~]# service iptables save      
#方式二:
~]# iptables-save > /etc/sysconfig/iptables  
#该目录为默认目录,也可以自定义保存路径
~]# iptables-save > /PATH/TO/SOME_RULE_FILE

CentOS 7:

~]# iptables -S > /PATH/TO/SOME_RULE_FILE
~]# iptables-save > /PATH/TO/SOME_RULE_FILE

从文件中加载预存的规则:

以下命令会自动从/etc/sysconfig/iptables文件中重载规则
通用方法:

~]# iptables-restore < /PATH/FROM/SOME_RULE_FILE

CentOS 6专用命令

service iptables restart

自动生效规则文件中的规则:

  • 把iptables命令放在脚本文件中,让脚本文件开机自动运行。
      把脚本放在/etc/rc.d/rc.local中即可

  • 用规则文件保存规则,开机自动运行重载命令
      在/etc/rc.d/rc.local中
      iptables-restore < /PATH/FROM/SOME_RULE_FILE

Docker的MySQL官方镜像设置时区

在 Docker Hub 中的 MySQL 官方镜像中,时区是使用了世界标准时间(UTC)。因为在中国使用,所以需要把时区改成东八区的。

方法1

1、查看当前时区

date -R

2、修改设置时区。先输入 tzselect 命令,然后根据提示,通过输入选项前面的数字来确定选项。我的例子,先选择 Asia ,再选择 Hong Kong。香港和中国采用了同样的东八区时间。

3、复制相应的时区文件,替换原来的时区文件。命令如下:

/usr/share/zoneinfo/Asia# cp Hong_Kong /etc/localtime

我们需要的时区文件在目录 /usr/share/zoneinfo/Asia 下。文件名字是 Hong_Kong。把它拷贝覆盖 /etc/localtime 文件。

4、重新输入命令 date -R,就可以看到修改时区后的中国时间了。

方法2

我自己找到了一种不用 tzselect 的方法。
目录/usr/share/zoneinfo/Asia下有各个亚洲地区的时区的文件。查看这个目录下的内容:

/usr/share/zoneinfo/Asia# ls -hl

lrwxrwxrwx 1 root root    6 Jul  6 02:15 Shanghai -> ../PRC
lrwxrwxrwx 1 root root   12 Jul  6 02:15 Singapore -> ../Singapore

从查询结果可以知道,上海的时区文件实际上是个软连接文件。连接到了目录 /usr/share/zoneinfo/ 下的PRC文件。
直接进行拷贝:

cp /usr/share/zoneinfo/PRC /etc/localtime

然后这样就可以了。

Docker下的web开发和Tomcat部署

本期实践的主要目标是开发一个简单的web应用,打包部署到Docker的tomcat容器中去; 第一期为了快速上手,获取docker是从国内的daocloud获取的,本期开始,为了更好的熟悉和了解Docker技术,我们的查找,pull和push都改为在Docker Hub上进行,即网站:hub.docker.com,建议各位去上面注册一个账号,这样就有自己的仓库可以保存镜像了。

在hub.docker.com上搜索tomcat,搜索结果的第一个就是官方镜像,如下图:

未分类

点击Detail按钮,进入详情页,可以发现有好多个tag,例如7.0.75这个,就是tomcat7.0.75版本:

未分类

这么多版本,究竟选哪个呢?我们还是先看看几个具体版本的差异吧,打开tomcat官网下的这个链接:tomcat.apache.org/whichversio…

可以看到具体的差异:

未分类

可以看到,tomcat7 支持servlet3.0,可以满足我们的要求了,所以就用它吧,执行如下命令行即可下载镜像:

docker pull tomcat:7.0.75

命令执行有可能执行失败,多重试几次才行,pull成功后用docker images命令可以看到镜像:

未分类

来快速体验一下镜像的效果,执行命令:

docker run -it --rm -p 8888:8080 tomcat:7.0.75

–rm参数表示container结束时,Docker会自动清理其所产生的数据。

可以看到tomcat启动的日志全部打印在终端了,

未分类

因为我们用-p 8888:8080将容器的8080端口映射到当前电脑的8888端口,所以打开当前电脑的浏览器,输入:localhost:8888,可以看到熟悉的大猫:

未分类

接下来我们开发一个最简单的spring mvc应用,然后部署到docker的tomcat容器中试试,这我用的是IntelliJ IDEA CE,创建maven工程:

未分类

GAV信息如下:

未分类

如下图所示,通过这里增加一个mvn命令:

未分类

增加mvn命令:

未分类

配置命令如下:

未分类

接下来我们给web工程添加spring mvc支持,首先是web.xml文件,替换成下面这样:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1" metadata-complete="true">

  <servlet>
    <servlet-name>spring</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>spring</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

</web-app>

完整的pom文件内容如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.bolingcavalry</groupId>
  <artifactId>helloworldwebapp</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>helloworldwebapp Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.5</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>4.3.4.RELEASE</version>
    </dependency>
  </dependencies>
  <build>
    <finalName>helloworldwebapp</finalName>
  </build>
</project>

在编译的时候遇到了一点小问题需要在此提一下,在工程上点击右键查看module属性,如下图:

未分类

此时看到的信息如下图所示:

未分类

注意在上图的绿色框框位置,如果你的工程中没有看到绿色框框中的内容,就用鼠标右键点击红色框框位置,在弹出的菜单中点击”Sources”,这样就把java目录加入到工程的编译目录中去了。

这时候去执行mvn命令依然无法编译java文件,在工程上点击右键,执行mvn的reimport命令,如下图,执行完毕后就可以用mvn命令编译java文件了:

未分类

现在开始添加测试代码,先增加一个view目录,里面放个jsp文件,文件结构和jsp文件的内容如下:

未分类

再增加一个java文件,文件路径如下:

未分类

该文件的源码:

package com.bolingcavalry.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class FirstController {

    @RequestMapping(value = "firstview", method = RequestMethod.GET)
    public String index() {
        return "firstview";
    }
}

最后在webapp/WEB-INF目录下增加spring-servlet.xml文件,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans.xsd 
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context.xsd 
       http://www.springframework.org/schema/tx 
       http://www.springframework.org/schema/tx/spring-tx.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- 配置扫描的包 -->
    <context:component-scan base-package="com.bolingcavalry.*" />

    <!-- 注册HandlerMapper、HandlerAdapter两个映射类 -->
    <mvc:annotation-driven />

    <!-- 访问静态资源 -->
    <mvc:default-servlet-handler />

    <!-- 视图解析器 -->
    <bean
        class-og="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/view/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

</beans>

好了,代码已经写完,可以打包了,执行我们刚才配置好的命令,如下图:

未分类

命令执行后,可以在target目录下看到war包:

未分类

现在我们把文件部署到tomcat上去,先建一个目录,例如我建了这个目录:/Users/bolingcavalry/temp/201703/10/share,然后把helloworldwebapp.war文件复制到这个目录下,再在控制台执行以下命令:

docker run --name helloworldwebapp -p 8888:8080 -d -v /Users/bolingcavalry/temp/201703/10/share:/usr/Downloads tomcat:7.0.75

这样就启动了一个容器,执行以下命令进入容器

docker exec -it helloworldwebapp /bin/bash

进入容器后再执行以下命令将war包复制到tomcat容器目录下:

cp /usr/Downloads/helloworldwebapp.war /usr/local/tomcat/webapps/

这时候再打开浏览器,输入http://localhost:8888/helloworldwebapp/firstview试试吧,如下图,符合预期:

未分类

此时,我们今天测试tomcat部署的目的已经达到了,接下来再试试提交镜像,在容器中输入exit 退出容器,再执行”docker stop helloworldwebapp”停止容器,然后执行以下命令把容器作为镜像保存在本地:

docker commit -a "bolingcavalry" -m "from tomcat 7.0.75,with a demo webapp"  helloworldwebapp bolingcavalry/helloworldwebapp:0.0.1

-a : 作者
-m :提交时的说明文字
0.0.1:tag

执行完毕后,输入docker images,可以看到新增的镜像:

未分类

接下来我们试着把本地镜像提交到hub.docker.com去(前提是已经在这个网站上注册过),输入命令docker login,接下来按照提示输入用户名和密码,执行一下命令提交镜像:

docker push bolingcavalry/helloworldwebapp:0.0.1

有点费时,需要等待:

未分类

等上传成功后,再去hub.docker.com上看看吧,自己的仓库下面已经可以看到刚刚提交的镜像了:

未分类

Docker端口映射与容器互联

除了通过网络访问外,Docker还提供了两个很方便的功能来满足服务访问的基本需求:

  • 一个是允许映射容器内应用的服务端口到本地宿主主机;
  • 另一个是互联机制实现多个容器间通过容器名来快速访问。

端口映射实现访问容器

从外部访问容器应用

在启动容器时,如果不指定对应的参数,在容器外是无法通过网络访问容器内的网络应用和服务的。

当容器中运行一些网络应用,要让外部访问这些应用时,可以通过-P或-p参数来指定端口映射。当使用-P(大写)标记时,Docker会随机映射一个49000~49900的端口到内部容器开放的网络端口。

-p(小写)可以指定要映射的端口,并且,在一个指定端口上只可以绑定一个容器。支持的格式有IP:HostPort:ContainerPort | IP::ContainerPort | HostPort:ContainerPort。

映射所有接口地址

使用HostPort:ContainerPort默认会绑定所有接口上的所有地址,多次使用-p标记可以绑定多个端口。

docker run -d -p 5000:5000 -p 3000:80 training/webapp python app.py

映射到指定地址的指定端口

可以使用IP:HostPort:ContainerPort格式指定映射使用一个特定地址。

docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py

映射到指定地址的任意端口

可以使用IP::ContainerPort格式绑定指定地址任意端口到容器端口,本地主机会自动分配一个端口。

docker run -d -p 127.0.0.1::5000 training/webapp python app.py

查看映射端口配置

使用docker port命令可以查看当前映射的端口配置,也可以查看到绑定的地址:

docker port nostalgic_morse 5000

注意: 容器有自己的内部网络和IP地址,使用docker inspect + 容器ID可以获取容器的具体信息。

互联网机制实现便捷互访

容器的互联是一种让多个容器中应用进行快速交互的方式。它会在源和接收容器之间创建连接关系,接收容器可以通过容器名快速访问到源容器,而不用指定具体的IP地址。

自定义容器命名

连接系统依据容器的名称来执行,因此首先需要定义一个好记的容器名,虽然创建容器时系统会默认分配一个名字。

使用–name标记可以为容器自定义命名:

docker run -d -P --name web training/webapp python app.py

注意: 容器的名称是唯一的。如果已经命名了一个叫web的容器,当要再次使用web这个名称的时候,需要先用docker rm来删除之前创建的同名容器。

容器互联

使用–link参数可以让容器之间安全地进行交互。

–link参数的格式为–link name:alias,其中name是要连接的容器名称,alias是这个连接的别名。

新建一个web容器,并将它连接到db容器:

docker run -d -P --name web --link db:db training/webapp python app.py

使用docker ps可以看到db容器的names列有db,也有web/db,这表示web容器连接到db容器,这允许web容器访问db容器的信息。

Docker相当于在两个互联的容器之间创建了一个虚机通道,而且不用映射它们的端口到宿主机上。

docker通过两种方式来为容器公开连接信息

  • 更新环境变量
  • 更新/etc/hosts文件

使用env命令来查看web容器的环境变量:

$ docker run -rm --name web --link db:db training/webapp env
...
DB_NAME=/web2/db
DB_PORT=tcp://172.17.0.5:5432
DB_PORT_5000_TCP=tcp://172.17.0.5:5432
DB_PORT_5000_TCP_PROTO=tcp
...

其中DB_开头的环境变量是供web容器连接db容器使用的,前缀采用大写的连接别名。

除了环境变量之外,Docker还添加host信息到父容器的/etc/hosts文件。

下面是父容器web的hosts文件:

$ docker run -rm --name web --link db:db training/webapp /bin/bash
$ cat /etc/hosts
172.17.0.7 aed84ee21bde
...
172.17.0.5 db

这里有两个hosts信息,第一个是web容器,web容器用自己的id作为默认主机名,第二个是db容器的IP和主机名。

用户可以连接多个子容器到父容器。

CentOS 7 加速 docker源

对于使用 systemd 的系统,请在 /etc/docker/daemon.json 中写入如下内容(如果文件不存在请新建该文件)

{
  "registry-mirrors": [
    "https://registry.docker-cn.com"
  ]
}

注意,一定要保证该文件符合 json 规范,否则 Docker 将不能启动。

之后重新启动服务。

$ sudo systemctl daemon-reload
$ sudo systemctl restart docker

注意:如果您之前查看旧教程,修改了 docker.service 文件内容,请去掉您添加的内容(–registry-mirror=https://registry.docker-cn.com),这里不再赘述。

如何做好Docker资源管控?

Docker资源管控配额原理

Docker同LXC一样,其对资源的隔离和管控是以Linux内核的namespaces和cgroups为基础。Docker的资源隔离使用了Linux内核 Kernel中的Namespaces功能来实现,隔离的对象包括:主机名与域名、进程编号、网络设备、文件系统的挂载点等,namespace中的IPC隔离docker并未使用,docker中使用TCP替代IPC。

在使用Namespaces隔离资源的同时,Docker使用了Linux内核Kernel提供的cgroups来对Container使用的CPU、内存、磁盘IO资源进行配额管控。换句话说,在docker的容器启动参数中,像—cpu、–memory和—blkio*的设置,实际上就是设置cgroups的相对应cpu子系统、内存子系统、磁盘IO子系统的配额控制文件,只不过这个修改配额控制文件的过程是docker实例代替我们做掉罢了。

因此,我们完全可以直接修改docker容器所对应的cgroup子系统中的配额控制文件来达到控制docker容器资源配额的同样目的,接下来将以IO控制为例,介绍一下具体实现过程。

物理部署架构图

实验测试环境物理部署图如下:

未分类

整体的部署思路如下:

1、 将物理磁盘进行分区操作(此步骤可选)
2、将分区创建为物理卷(PV)并添加到卷组(VG)中
3、从卷组(VG)中创建出逻辑卷(LV),并将该卷格式化后挂载供docker使用
4、将步骤3中逻辑卷挂载点再挂载到docker镜像容器中

管控配额过程

1、将磁盘设备/dev/sdb进行分区,分别为sdb1和sdb2

未分类

2、创建PV

pvcreate /dev/sdb2  #使用sdb2创建物理卷(PV)

未分类

3、创建VG

vgcreate datavg /dev/sdb2 #vg名称为datavg

未分类

4、创建LV,并格式化

lvcreate  -n  datalv  datavg  && mkfs.ext4 /dev/mapper/datavg-datalv

未分类

5、创建目录并挂载

mkdir /data && mount /dev/mapper/datavg-datalv /data

未分类

测试验证

1、使用docker镜像docker.io/learn/tutorial :jiangzt作为测试容器

未分类

2、不加配额参数的情况下启动容器

启动容器时不配额参数,并观察其所在的cgroups子系统的配置情况。

docker run -dit -h inner --name test -v /data:/data a36927dbb31f /bin/bash

未分类

根据容器ID观察cgroups下blkio子系统的配置情况,结果是文件blkio.throttle.write_bps_device中没有内容。

未分类

进入容器内,执行dd命令,测试观察此时默认的写入IO能力。

docker exec -it test  /bin/bash
time dd if=/dev/zero of=/data/test.out bs=1M count=1024 oflag=direct

未分类

可以得知写入的平均速度是 2.1GB/s

3、使用参数device-write-bps启动容器

启动容器增加配额参数device-write-bps,写入目标速度为1mb/s,并观察其所在的cgroups子系统的配置情况。

docker run -dit  -m 100m -h inner --name test --device-write-bps /dev/dm-6:1mb  -v /data:/data a36927dbb31f /bin/bash

说明:lv的设备名称/dev/dm-6可以通过dmsetup做查询

未分类

容器启动后根据容器id观察cgroups子系统中的配置情况。

未分类
未分类

在cgroups的blkio.throttle.write_bps_device文件中观察到了253:6 1048576 就是我们在启动docker时指定的–device-write-bps参数值,设备为253:6(/dev/dm-6),io能力为1048576byte(1mb)

进入容器内,执行dd命令,测试观察此时默认的写入IO能力

未分类

可以观察到此时的写入速度为1.0MB/s

4、直接修改cgroup中的参数配置

前面在原理介绍时提过,docker其实也是修改cgroup中的参数来控制资源的使用,那么我们直接修改blkio.throttle.write_bps_device,然后观察是否能直接作用于docker容器,还是使用上面的容器id进行操作,将blkio.throttle.write_bps_device中的内容修改为10MB/s。
echo 253:6 10485760 > blkio.throttle.write_bps_device
进入容器内,执行dd命令,测试观察此时默认的写入IO能力。

未分类

可以看到通过直接修改cgroup中的blkio.throttle.write_bps_device的速度值起到了作用。

5、对于其他参数的控制分析

细心的你可能已经发现在docker镜像时,还有个参数-m 100m,该参数是用来控制容器使用内存的配额,我们可以通过docker stats进行验证。

未分类

同样我们也是可以在cgroup的内存子系统中看到相应的参数配置。

总结

通过以上的分析测试,进一步熟悉了解了docker运行时的行为和所依赖的底层的技术原理,希望能够为docker的自动化运维开发提供解决思路和办法。

awk 非排序和 sort uniq 排序处理文件的交叉并集合

日常工作中,经常会有文件处理。要玩的转命令,记得熟,速度快。

如题,指定列去重就是一个频繁遇到的问题。脑子里一直深深烙着 sort | uniq 这种用法,直到前两天处理一个2700万+行的大文件,才意识到老朋友玩不转了。。

对千万行的大文件执行 sort | uniq,等的花儿谢了也白扯… 显然,咱手头的文件列也不是带索引的(又不是数据库),那执行大数据排序 sort 操作当然非常慢了。 更快的方法,当然要考虑怎么不用排序去重。

熟悉编程的同学,肯定立马想到用对象数组啊,key=>value这种。因为key是唯一的(文件指定的去重列做key),这样不就是去重了么~

嗯啊,下边介绍的这种机智的命令就是应用的这个想法。 大杀器: !arr[key]++

简洁的表达,反而容易迷糊住一般人。简单说下就明白了。

  • arr即是定义一个数组
  • key即是文件的指定列
  • 运算符优先级:a++ 优先于 !

再啰嗦一句就捅破了.. 按照文件行处理的话:第一次的key,arr[key]++ 表达式返回0,加个!,表达式结果为true。此时arr[key]==1了,后续行再遇到这个相同key, !arr[key]++ 这个表达式自然就是false了。

明白这个表达式了,那命令也出来了,利用下awk。比如指定列是第一列

awk '!arr[$1]++' filename

再说下awk是’条件{do sth}'!arr[$1]++ 是条件,命令默认打印全部{print $0}

这种去重方式,显然不需要预先排序,速度自然快的飞起哈哈。不过速度是快了,牺牲了什么呢?他疯狂的吃内存,因为它要存下来整个文件列的数组。很大的文件情况下就要考虑内存承受了。

我的开发机,在搞这2700万+的文件就吃不消了,进程过一会被杀死了。执行过程中,top一下会发现这个命令内存占比蹭蹭往上涨。
但是 sort | uniq 这种方式,利用内存上会更好,不会挂掉。

因为开发机内存不够的原因,为了展示下这两个方法的数据对比。我按每900万行拆了下文件。(实际文件是两列,指定第一列去重)

split -l 9000000 userimei.log

利用分割后的文件 xaa
对比:

#!/bin/bash

time awk '{print $1}' xaa | sort | uniq > zaa.log

time awk '!arr[$1]++{print $1}' xaa > zbb.log

time 是查看命令执行时间。

1、sort | uniq 方法耗时 92秒

real    1m32.192s
user    1m27.186s
sys     0m1.529s

2、 !arr[key]++ 方法耗时 24秒

real    0m23.902s
user    0m20.660s
sys     0m1.602s

查看去重后文件行数是否一致:

wc -l zaa.log

wc -l zbb.log

都是 8942497 行,符合预期。

awk !arr[key]++ 这种用法,手痒简单写个php处理。

<?php

ini_set('memory_limit', '1024M');

$filename = 'xaa';

$file = fopen($filename, "r");

// 按照awk一行一行读取。大文件处理方式
while(!feof($file)){
    $lineArr = explode("t", fgets($file));// 本文件两列
    $key = $lineArr[0];// 指定第一列

    if(!isset($arr[$key])){
        $arr[$key] = null;
    }
}

fclose($file);

file_put_contents('zcc.log', implode("n", array_keys($arr)));

是不是真正掌握了大杀器呢?

举一反三: 比如交集、并集、差集这些集合运算呢?
a.log 和 b.log 是各自去重了的A集合和B集合(集合性质之一互异性)。

交集

1. sort | uniq 方法 -d 参数只输出重复行
    sort a.log b.log | uniq -d

2. arr[$1]++ 方法该怎么办呢?真正掌握了的话,就知道表达式等于1输出就ok了
    awk 'a[$1]++==1' a.log b.log

仍然用了前面900万行的文件a,和从中选取两行做b;实际对比:

# 方法1
real    0m51.684s

# 方法2
real    0m18.216s

并集

1. sort | uniq 方法,直接就是去重
    sort a.log b.log | uniq

2. arr[$1]++  这个就是去重而已。表达式为0 或者 非!就ok了
    awk '!a[$1]++' a.log b.log

仍然用了前面900万行的文件a,和从中选取两行做b;实际对比:

# 方法1
real    0m52.436s

# 方法2
real    0m19.080s

差集

差集需要注意顺序,是A-B or B-A

A-B 举例:

# sort | uniq 方法,-u 参数只输出出现一次的记录。一个技巧,使用两次B集合完成 A-B 差集运算
1.  sort a.log b.log b.log | uniq -u

# arr[$1] 杀器,设计这种方法花费了我好长时间。。也写出来几个,但每写一个总感觉有更快的,最后选择了下面这个方法。即便这种写法的速度也大大优越于排序的方法。
2.  awk 'NR<=FNR{a[$1]}NR>FNR{delete a[$1]}END{for(k in a) print k}' a.log b.log

A a.log 是3条数据;B b.log是300万条数据。
开始测试:

A-B: (小文件-大文件)
# 方法1
real    0m52.710s

# 方法2
real    0m1.891s

这效果杠杠滴把哈哈~

----------------------

B-A: (大文件-小文件)
# 方法1
real    0m22.575s   

# 方法2
real    0m7.930s 

----------------------

当小文件-大文件的差集时,第二种方法远远优于第一种排序方法。

同时也发现,数组这种方法,在处理上,时间更多是耗费在对数组赋值上(赋值操作是前一个文件)。

再简单介绍下差集中的 awk 命令:
NR 表示处理当前的行数,不管awk一个文件还是两个文件,NR始终都是 +1 递增的。
FNR 表示当前文件处理的行数,类似NR的是,当处理各自文件时是 +1 递增的;而区别与NR,FNR处理新文件时,是从 1 开始了。

这样语句就好解释了,当NR在第一个文件a.log时,始终赋值列到数组a里。当NR在第二个文件b.log时,始终删除a数组列。哈哈,其实这不就是差集的定义么?在 A 集合,但又不在 B 集合。

==============================================================================================================
Okay了~ 写篇博客真耗时啊… 写完是真开心!

当遇到大文件不需要排序的去重 或者 交差并集合运算时候,利用 !arr[$1]++ 起来吧~ 会给你节省时间的哈哈

如何在 Linux / Unix 上使用 awk 打印文件名

Q:我想在 Linux / 类Unix 系统上使用 awk 打印文件名。 如何使用 awk 的 BEGIN 特殊模式打印文件名? 我可以使用 gawk/awk 打印当前输入文件的名称吗?(LCTT 译注:读者最好能有一些 awk 的背景知识,否则阅读本文的时候会有一些困惑)

在 FILENAME 变量中存放着当前输入文件的名称。 您可以使用 FILENAME 显示或打印当前输入文件名,如果在命令行中未指定文件,则 FILENAME 的值为 – (标准输入)(LCTT 译注:多次按下回车键即可看到效果)。 但是,除非由 getline 设置,否则 FILENAME 在 BEGIN 特殊模式中未定义。

使用 awk 打印文件名

使用语法如下:

awk '{ print FILENAME }' fileNameHere 
awk '{ print FILENAME }' /etc/hosts

因 awk 逐行读取文件,因此,你可能看到多个文件名,为了避免这个情况,你可以使用如下的命令:(LCTT 译注:FNR 表示当前记录数,只在文件中有效)

awk 'FNR == 1{ print FILENAME } ' /etc/passwd 
awk 'FNR == 1{ print FILENAME } ' /etc/hosts

未分类

使用 awk 的 BEGIN 特殊规则打印文件名

使用下面的语法:(LCTT 译注:ARGV[I] 表示输入的第 i 个参数)

awk 'BEGIN{print ARGV[1]}' fileNameHere 
awk 'BEGIN{print ARGV[1]}{ print "someting or do something on data" }END{}' fileNameHere 
awk 'BEGIN{print ARGV[1]}' /etc/hosts

示例输出:

/etc/hosts

然而,ARGV[1] 并不是每一次都能奏效,例如:

ls -l /etc/hosts | awk 'BEGIN{print ARGV[1]} { print }'

你需要将它修改如下(假设 ls -l 只产生一行输出):

ls -l /etc/hosts | awk '{ print "File: " $9 ", Owner:" $3 ", Group: " $4 }'

示例输出:

File: /etc/hosts, Owner:root, Group: root

处理由通配符指定的多个文件名
使用如下的示例语法:

awk '{ print FILENAME; nextfile } ' *.c 
awk 'BEGIN{ print "Starting..."} { print FILENAME; nextfile }END{ print "....DONE"} ' *.conf

示例输出:

Starting...
blkid.conf
cryptconfig.conf
dhclient6.conf
dhclient.conf
dracut.conf
gai.conf
gnome_defaults.conf
host.conf
idmapd.conf
idnalias.conf
idn.conf
insserv.conf
iscsid.conf
krb5.conf
ld.so.conf
logrotate.conf
mke2fs.conf
mtools.conf
netscsid.conf
nfsmount.conf
nscd.conf
nsswitch.conf
openct.conf
opensc.conf
request-key.conf
resolv.conf
rsyncd.conf
sensors3.conf
slp.conf
smartd.conf
sysctl.conf
vconsole.conf
warnquota.conf
wodim.conf
xattr.conf
xinetd.conf
yp.conf
....DONE

nextfile 告诉 awk 停止处理当前的输入文件。 下一个输入记录读取来自下一个输入文件。 更多信息,请参见 awk/gawk[1] 命令手册页https://www.gnu.org/software/gawk/manual/

man awk 
man gawk