数据专栏

智能大数据搬运工,你想要的我们都有

科技资讯:

科技学院:

科技百科:

科技书籍:

网站大全:

软件大全:

「深度学习福利」大神带你进阶工程师,立即查看>>>
摘要: 这是本系列的第2篇内容,将介绍在Docker和Kubernetes环境中解决遗留应用无法识别容器资源限制的问题。
本系列文章记录了企业客户在应用Kubernetes时的一些常见问题 第一篇:Java应用资源限制的迷思 第二篇:利用LXCFS提升容器资源可见性 第三篇:解决服务依赖
这是本系列的第2篇内容,将介绍在Docker和Kubernetes环境中解决遗留应用无法识别容器资源限制的问题。
Linuxs利用Cgroup实现了对容器的资源限制,但在容器内部依然缺省挂载了宿主机上的 procfs 的 /proc 目录,其包含如:meminfo, cpuinfo,stat, uptime等资源信息。一些监控工具如free/top或遗留应用还依赖上述文件内容获取资源配置和使用情况。当它们在容器中运行时,就会把宿主机的资源状态读取出来,引起错误和不便。
LXCFS简介
社区中常见的做法是利用 lxcfs 来提供容器中的资源可见性。lxcfs 是一个开源的FUSE(用户态文件系统)实现来支持LXC容器,它也可以支持Docker容器。
LXCFS通过用户态文件系统,在容器中提供下列 procfs 的文件。 /proc/cpuinfo /proc/diskstats /proc/meminfo /proc/stat /proc/swaps /proc/uptime
LXCFS的示意图如下
比如,把宿主机的 /var/lib/lxcfs/proc/memoinfo 文件挂载到Docker容器的 /proc/meminfo 位置后。容器中进程读取相应文件内容时,LXCFS的FUSE实现会从容器对应的Cgroup中读取正确的内存限制。从而使得应用获得正确的资源约束设定。
Docker环境下LXCFS使用
注: 本文采用CentOS 7.4作为测试环境,并已经开启FUSE模块支持。 Docker for Mac/Minikube等开发环境由于采用高度剪裁过的操作系统,无法支持FUSE,并运行LXCFS进行测试。
安装 lxcfs 的RPM包 wget https://copr-be.cloud.fedoraproject.org/results/ganto/lxd/epel-7-x86_64/00486278-lxcfs/lxcfs-2.0.5-3.el7.centos.x86_64.rpm yum install lxcfs-2.0.5-3.el7.centos.x86_64.rpm
启动 lxcfs lxcfs /var/lib/lxcfs &
测试 $docker run -it -m 256m \ -v /var/lib/lxcfs/proc/cpuinfo:/proc/cpuinfo:rw \ -v /var/lib/lxcfs/proc/diskstats:/proc/diskstats:rw \ -v /var/lib/lxcfs/proc/meminfo:/proc/meminfo:rw \ -v /var/lib/lxcfs/proc/stat:/proc/stat:rw \ -v /var/lib/lxcfs/proc/swaps:/proc/swaps:rw \ -v /var/lib/lxcfs/proc/uptime:/proc/uptime:rw \ ubuntu:16.04 /bin/bash root@f4a2a01e61cd:/# free total used free shared buff/cache available Mem: 262144 708 261436 2364 0 261436 Swap: 0 0 0
我们可以看到total的内存为256MB,配置已经生效。
lxcfs 的 Kubernetes实践
一些同学问过如何在Kubernetes集群环境中使用lxcfs,我们将给大家一个示例方法供参考。
首先我们要在集群节点上安装并启动lxcfs,我们将用Kubernetes的方式,用利用容器和DaemonSet方式来运行 lxcfs FUSE文件系统。
本文所有示例代码可以通过以下地址从Github上获得 git clone https://github.com/denverdino/lxcfs-initializer cd lxcfs-initializer
其manifest文件如下 apiVersion: apps/v1beta2 kind: DaemonSet metadata: name: lxcfs labels: app: lxcfs spec: selector: matchLabels: app: lxcfs template: metadata: labels: app: lxcfs spec: hostPID: true tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule containers: - name: lxcfs image: registry.cn-hangzhou.aliyuncs.com/denverdino/lxcfs:2.0.8 imagePullPolicy: Always securityContext: privileged: true volumeMounts: - name: rootfs mountPath: /host volumes: - name: rootfs hostPath: path: /
注: 由于 lxcfs FUSE需要共享系统的PID名空间以及需要特权模式,所有我们配置了相应的容器启动参数。
可以通过如下命令在所有集群节点上自动安装、部署完成 lxcfs,是不是很简单?:-) kubectl create -f lxcfs-daemonset.yaml
那么如何在Kubernetes中使用 lxcfs 呢?和上文一样,我们可以在Pod的定义中添加对 /proc 下面文件的 volume(文件卷)和对 volumeMounts(文件卷挂载)定义。然而这就让K8S的应用部署文件变得比较复杂,有没有办法让系统自动完成相应文件的挂载呢?
Kubernetes提供了 Initializer 扩展机制,可以用于对资源创建进行拦截和注入处理,我们可以借助它优雅地完成对lxcfs文件的自动化挂载。
注: 阿里云Kubernetes集群,已经默认开启了对 Initializer 的支持,如果是在自建集群上进行测试请参见 文档 开启相应功能
其 manifest 文件如下 apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: lxcfs-initializer-default namespace: default rules: - apiGroups: ["*"] resources: ["deployments"] verbs: ["initialize", "patch", "watch", "list"] --- apiVersion: v1 kind: ServiceAccount metadata: name: lxcfs-initializer-service-account namespace: default --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: lxcfs-initializer-role-binding subjects: - kind: ServiceAccount name: lxcfs-initializer-service-account namespace: default roleRef: kind: ClusterRole name: lxcfs-initializer-default apiGroup: rbac.authorization.k8s.io --- apiVersion: apps/v1beta1 kind: Deployment metadata: initializers: pending: [] labels: app: lxcfs-initializer name: lxcfs-initializer spec: replicas: 1 template: metadata: labels: app: lxcfs-initializer name: lxcfs-initializer spec: serviceAccountName: lxcfs-initializer-service-account containers: - name: lxcfs-initializer image: registry.cn-hangzhou.aliyuncs.com/denverdino/lxcfs-initializer:0.0.2 imagePullPolicy: Always args: - "-annotation=initializer.kubernetes.io/lxcfs" - "-require-annotation=true" --- apiVersion: admissionregistration.k8s.io/v1alpha1 kind: InitializerConfiguration metadata: name: lxcfs.initializer initializers: - name: lxcfs.initializer.kubernetes.io rules: - apiGroups: - "*" apiVersions: - "*" resources: - deployments
注: 这是一个典型的 Initializer 部署描述,首先我们创建了service account lxcfs-initializer-service-account ,并对其授权了 "deployments" 资源的查找、更改等权限。然后我们部署了一个名为 "lxcfs-initializer" 的Initializer,利用上述SA启动一个容器来处理对 "deployments" 资源的创建,如果deployment中包含 initializer.kubernetes.io/lxcfs 为 true 的注释,就会对该应用中容器进行文件挂载
我们可以执行如下命令,部署完成之后就可以愉快地玩耍了 kubectl apply -f lxcfs-initializer.yaml
下面我们部署一个简单的Apache应用,为其分配256MB内存,并且声明了如下注释 "initializer.kubernetes.io/lxcfs": "true"
其manifest文件如下 apiVersion: apps/v1beta1 kind: Deployment metadata: annotations: "initializer.kubernetes.io/lxcfs": "true" labels: app: web name: web spec: replicas: 1 template: metadata: labels: app: web name: web spec: containers: - name: web image: httpd:2 imagePullPolicy: Always resources: requests: memory: "256Mi" cpu: "500m" limits: memory: "256Mi" cpu: "500m"
我们可以用如下方式进行部署和测试 $ kubectl create -f web.yaml deployment "web" created $ kubectl get pod NAME READY STATUS RESTARTS AGE web-7f6bc6797c-rb9sk 1/1 Running 0 32s $ kubectl exec web-7f6bc6797c-rb9sk free total used free shared buffers cached Mem: 262144 2876 259268 2292 0 304 -/+ buffers/cache: 2572 259572 Swap: 0 0 0
我们可以看到 free 命令返回的 total memory 就是我们设置的容器资源容量。
我们可以检查上述Pod的配置,果然相关的 procfs 文件都已经挂载正确 $ kubectl describe pod web-7f6bc6797c-rb9sk ... Mounts: /proc/cpuinfo from lxcfs-proc-cpuinfo (rw) /proc/diskstats from lxcfs-proc-diskstats (rw) /proc/meminfo from lxcfs-proc-meminfo (rw) /proc/stat from lxcfs-proc-stat (rw) ...
在Kubernetes中,还可以通过 Preset 实现类似的功能,篇幅有限。本文不再赘述了。
总结
本文介绍了通过 lxcfs 提供容器资源可见性的方法,可以帮助一些遗留系统更好的识别容器运行时的资源限制。
同时,在本文中我们介绍了利用容器和DaemonSet的方式部署lxcfs FUSE,这不但极大简化了部署。也可以方便地利用Kubernetes自身的容器管理能力,支持lxcfs进程失效时自动恢复,在集群伸缩时也可以保证节点部署的一致性。这个技巧对于其他类似的监控或者系统扩展都是适用的。
另外我们介绍了利用Kubernetes的扩展机制 Initializer,实现对 lxcfs 文件的自动化挂载。整个过程对于应用部署人员是透明的,可以极大简化运维复杂度。同时利用类似的方法,我们可以灵活地定制应用部署的行为,满足业务的特殊要求。
阿里云Kubernetes服务 全球首批通过Kubernetes一致性认证,简化了Kubernetes集群生命周期管理,内置了与阿里云产品集成,也将进一步简化Kubernetes的开发者体验,帮助用户关注云端应用价值创新。
云计算
2018-04-17 14:01:00
「深度学习福利」大神带你进阶工程师,立即查看>>> 本文由4月12日晚姜江,新东方运维工程师所做的技术分享整理而成。
姜江,一名伪SRE。十余年IT运维从业经历,早年在金融行业从事linux,AIX,Oracle的运维支持,现在专注于运维自动化、容器和cloud native相关技术的研究和落地。目前就职于新东方,负责新东方容器云平台的探索和实践。
搜索微信号RancherLabsChina,添加Rancher小助手为好友,可加入官方技术交流群,实时参加下一次分享~
大家好!话说加入Rancher官方微信群也有三年多了,每天在群里看着大家讨论技术问题给我很多启发 。 我今天也来和大家分享一下我在rancher上的一些实践,希望能为各位的实践提供一些参考。
为了避免大家歧义,下面说的Rancher指的 Rancher 1.6 上的cattle引擎 。
摘要
Rancher是一个对用户很友好的产品,吸引了大批的用户和粉丝。在实际落地过程中,大家可能已经注意到Rancher组件之间的耦合存在单点。那么如何部署才能提高Rancher的可用性和可扩展性呢?Rancher在官方文档中给出了一些指导方案,用户可以根据自己的需要选择合适的方案落地。我今天和大家分享的是新东方在Rancher全组件负载均衡架构的实践,供大家在探索自己的高可用方案时做个参考。
1. Rancher的部署架构演变
Rancher 1.6是一套小巧而实用的容器管理平台,我个人认为Rancher的定位应该是容器编排引擎的后台支撑系统, 或者说是编排引擎的系统,因此Rancher1.6的架构并不是分布式架构,而是传统的C/S架构。 无数的client(agent)连接到一个中心的Server ,中心的Server连接一个数据库做持久化。这种架构就必然会涉及一个老话题,如何增强C/S之间的高可用性、可扩展性和解耦问题。我今天和大家聊内容是:Rancher 1.6 全组件负载均衡设计。
一般Rancher的简单部署方案是这样的: 一个server,一些节点 ,一个mysql数据库。从这张图上就可以发现Rancher在部署上的一些问题: SPOF问题,Server的单点,数据库的单点等。 扩展性问题,单server能带动的client数量有限。 强耦合问题,server 写死数据库地址,client写死server地址等等。
总结下来就是下面这个图:
下面就对这个架构进行优化,我们的目标是达到Server/Client 和数据库三个组件的自由扩展,如何做到呢? Rancher官方文档中已经给了答案,只需要在C/S 中间增加一层负载均衡方案,在数据库一侧增加高可用方案即可。
总体架构就演变为这样:
我们看到,Server一侧使用Rancher Server HA方案,将server扩展为多个。
中间增加一层负载均衡,这个负载均衡常见的私有云方案有LVS+Nginx或者F5, 在公有云中可以使用阿里云的SLB或者AWSELB等等。Rancher的数据量并不算太大,可以简单的使用Mysql的主从方案。
优化到这步已经可以满足日常工作需要了,这里大家会注意到mysql主从还是会影响server的可靠性。 一旦出现主库问题,Mysql切换的时候server肯定就down了, 切换也需要一定的时间,启动后还需要重启server等等。如果希望更进一步提高server的可靠性,或者希望数据库切换对Rancher Server透明,那就需要使用更高级的数据库高可用方案。
我们的DBA推荐使用Maxscale中间件+Galera 集群的方案, Galera为多主库的分布式数据方案,发生写入操作后Galera会同步数据到其他数据库中,直到同步都完成后返回完成写入操作。MaxScale是数据库中间件,它通过解析mysql协议判断读写操作,可以将读写操作分离。一旦数据库发生故障整个数据库集群的切换对外界是无感知的。
这个方案的部署图是这样的:
每个组件都介绍一下, 负载均衡采用互联网最常用的LVS-DR+Nginx方案。数据库采用Galera集群做多主库的数据库复制,通过Maxscale中间件来做高可用和读写分离。
这就是我们现在最终的架构, 这个架构的好处有几点: 可靠性大大增加, 所有组件都可以扩展,server, client,数据库和负载均衡本身。 每个组件的扩展并不影响其他组件,所有组件的扩展都可以在线进行。 配置解耦,通过域名映射的方式,server连接数据库的域名映射为maxsacle中间件的ip, client连接server的域名映射为负载均衡的Vip。 故障切换透明,对其他组件无感知。数据库故障,server故障都可以在线解决,解决后重新加入集群。
顺便提一下,这个架构中的数据库部分还可以进一步优化为:
MaxScale前增加一层LVS四层负载均衡(设备可复用c/s之间的负载均衡),那么这个方案就可以说是全模块无死角的负载均衡架构了。 但是我们实际实施的时候并没有采用这样的方案,考虑到 Rancher Server 和 Client断开一段时间后并不会影响主机上的容器运行。
这段时间完全可以做一些维护, 采用 Galera和Maxscale后已经大大的减少了数据库切换时间,并且实测MaxScale的稳定性很好,感觉没有必要再投入更多资源在MaxScale上。更何况加入更多的组件会使得整个系统的复杂度上升,这实际上增加了维护成本并且扩展了故障域,可靠性有可能不升反降。因此这个方案也只是停留在纸面上了。
那么说到这里大家对整体架构就有了初步认识,下面说说实现。
篇幅和时间原因,我这里只是点一下配置的要点,具体的实践过程可以关注我的工作笔记 http://jiangjiang.space
2. Rancher各个组件负载均衡的配置
2.1 LVS-DR + Nginx
LVS-DR设置:
LVS1 : 配置keepalived,配置虚拟ip,配置到Nginx1和Nginx2的四层转发
LVS2 : 配置keepalived,配置虚拟ip,配置到Nginx1和Nginx2的四层转发
Nginx1 :配置nginx 到 rancher server 8080 上的七层转发
Nginx2 :配置nginx 到 rancher server 8080 上的七层转发
**A. 安装keepalived 和 ipvsadmin **
LVS从2.6内核开始就已经是内核的一部分了,因此只需要安装ipvsadmin和keepalived,通过yum安装或者源码安装都可以,步骤略过。
B. 配置keepalived.conf
vi /etc/keepalived/conf/rancher.conf
**C. Nginx节点配置 **
Nginx节点配置分为两部分:
1.虚拟IP(VIP)设置到回环设备上(lo),当接收到lvs发来的包后本机网卡才会处理这些包。
2.设置Nginx的Websocket转发到rancher server的8080端口上。
VIP配置在lo上的配置:
下面设置Nginx的Websocket转发到Rancher server的8080端口上。 安装nginx过程略过,在conf.d中增加虚拟主机配置。 vi /usr/local/nginx/conf/conf.d/rancher.conf
配置好后,直接访问 http://VIP:80 应该就可以看到Rancher控制台界面了。如果没有则是配置错误,继续调整。
参考文档:
https://www.cnblogs.com/liwei0526vip/p/6370103.html
https://rancher.com/docs/rancher/v1.6/en/installing-rancher/installing-server/basic-ssl-config/#example-nginx￾configuration
2.2 galera 和 Maxscale
以下步骤摘自新东方资深DBA傅少峰的文档。
https://mariadb.com/downloads 下载并安装mariadb 10.2
安装如下rpm包
MariaDB-client-10.2.11-1.el7.centos.x86_64
MariaDB-devel-10.2.11-1.el7.centos.x86_64
MariaDB-server-10.2.11-1.el7.centos.x86_64
MariaDB-common-10.2.11-1.el7.centos.x86_64
MariaDB-compat-10.2.11-1.el7.centos.x86_64
galera-25.3.22-1.rhel7.el7.centos.x86_64.rpm
jemalloc-3.6.0-1.el7.x86_64.rpm
jemalloc-devel-3.6.0-1.el7.x86_64.rpm
maxscale-2.1.16-1.centos.7.x86_64.rpm
maxscale-devel-2.1.16-1.centos.7.x86_64.rpm
maxscale可以复用其中一个数据库节点或者单独部署到一个机器。
mysql配置注意的地方,其他参数省略。
galera配置
执行:
建立cattle数据库和用户(连接任何一个galera实例执行):
创建maxscale监控用户(连接任何一个galera实例执行):
数据库准备完毕,下面是配置MaxScale。
MaxScale配置
配置文件: /etc/maxscale.cnf
启动 maxscale
参考资料:
https://www.jianshu.com/p/772e17c10e08
https://rancher.com/docs/rancher/v1.6/en/installing-rancher/installing-server/#single-container-external-database
https://linux.cn/article-5767-1.html#3_2234
2.3 server 、client 和 数据库的连接和设置
数据库准备好了,下面就可以启动server了。启动过程非常简单,只需要修改rancher server的启动参数如下:
逐条解释一下:
Server启动后就可以增加client了,登陆Rancher管理控制台,选择infrastructure->Hosts->Add Host
这样一个完整的负载均衡的Rancher就搭建完成了。
3 应用交付和负载均衡
现在说最后一个话题,应用交付和负载均衡。 所谓应用交付说的是如何将Rancher上的一个应用公布到公网上给用户使用。为了能在前方接住大量的用户请求,一般都会统一搭建站点级别的入口负载均衡。示意图如下:
如图所示,用户请求从公网进来后,通过各路ISP接入设备到达站点边缘,连接通过各种防御设备到达入口负载均衡(LVS+Keepalived+Nginx),再由入口负载均衡统一代理到内部源站点上,这就是常见的应用交付过程。
在Rancher上的应用对接入口负载均衡一般采用Traefik, Traefik是一个容器化7层代理软件,性能接近Nginx。 之前群里也有多位大拿分享过Traefik的应用, 我这里就不重复介绍了,大家可以参考爱医康架构师张新峰的分享:《关于高可用负载均衡的探索》
我下面要讲的是 traefik与 LVS+Nginx负载均衡的对接和我对traefik的一些实践。
利用traefik交付应用的整个过程我总结为下图: 用户在浏览器中输入www.myxdf.com,通过DNS查询到这个域名对应的公网ip地址。 这个公网ip地址指向我们站点的入口负载均衡(LVS+Nginx),入口负载均衡收到请求后根据域名的设置,将请求转发给Rancher 上的Traefik节点,我称这些节点为Ranhcer edge。 traefik收到请求后,根据traefik.frontend.rule将请求转给打了标记的容器(或者rancher lb)
根据这个流程我们需要做以下几个配置。
3.1 定义边界(rancher edge host)
什么是边界? 边界就是Rancher cluster中对外转发的中转站,边界是特殊的一个Rancher host,边界上只跑treafik,其他什么容器都不跑,边界最好也不要接入任何存储(比如Rancher nfs),边界需要做严格的安全加固。 当然也可以不将边界放在专门的host上,可以将边界混合到整个Rancher 集群中,这样的好处是节省计算资源,两种模式都可以。
下面介绍如何将host定义为专门的边界节点:在Ranhcer 控制台上, 打开 infrastructure ,点击Hosts中。找到作为边界的host点击Edit:
在Labels中增加label,增加这个label后traefik就会自动运行在这台host上。
在 Required Container Label 中随便增加一个lable不用给值, 我这里写的cn.xdf.edge
这个lable加入后,这个节点上就不会被调度任何其他容器了。这样一个边界节点就制作好了。
3.2 启动traefik 到 边界节点
在社区商店中找到这个可爱的小交警,启动traefik。
配置说明如下:
好了启动traefik后,容器已经在edge Host 上启动了!
登陆管理界面看看:
3.3 在traefik上发布应用服务
假设现在要把mydockerapp04.myxdf.com这个域名的应用发布出去。 首先将mydockerapp04.myxdf.com这个域名匹配到对应的Rancher stack上面去。
这个就是我们的mydockerapp04应用, 它是一些tomcat容器和一个内部lb组成的。
按照traefik的文档,只需要将label打到容器上即可,这里我的做法是增加了一个内部的lb,直接将label打在lb上。这里必须的label是:
label加入后应用就发布到traefik中了。
3.4 从站点入口负载均衡转发到Traefik
在站点入口负载均衡上做转发配置,转发到treafik边界节点。 负载均衡上所有源站都是通过80端口转发,通过设置不通的server_name来区分转发目的地。将server_name设置为*.myxdf.com 。意思是所有 myxdf.com的二级域名全部转发给traefik,这样做是为了跳过在入口负载均衡上配置明细转发规则(比如mydockerapp01转发到哪里这样的一条一条的规则),所有明细的转发规则全部交给traefik来管理。
这样做有很大的优势,我们之前也看到了traefik设置转发规则非常简单,只需要在容器或者ranhcer lb上打label即可。如果需要修改或者删除规则,也只需要修改或删除对应的label即可,管理灵活又免去了每次都要手动到入口负载均衡上刷新配置的麻烦。
Nginx配置如下:
3.5 在DNS上设置域名指向站点入口负载均衡
在DNS上添加域名指向, 这里就没有办法偷懒了,需要把所有明细域名都写上。
如果你不是公网应用,也可以使用dnsmasq或者bind 甚至 AD域的DNS 等在内网建立DNS指向,DNS指向的IP为内网LVS的vip。
Rancher里面也有修改DNS的APP,比如aliyun DNS、Gandi.net 等等。这些APP有的是自动添加DNS记录的,有的是根据label添加,大家有兴趣可以继续研究下。
至此, 整个转发过程就完成了。 用户已经可以通过外网访问这个应用了。
通过浏览器:打开 mydockerapp04.myxdf.com
当然这个域名其实并不存在,是我瞎掰的,只为了方便大家理解这个过程。那么今天的分享也就结束了。
云计算
2018-04-16 09:32:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
应用启动过程中可能需要一些敏感信息,比如访问数据库的用户名密码或者秘钥。将这些信息直接保存在容器镜像中显然不妥,Kubernetes 提供的解决方案是 Secret。
Secret 会以密文的方式存储数据,避免了直接在配置文件中保存敏感信息。Secret 会以 Volume 的形式被 mount 到 Pod,容器可通过文件的方式使用 Secret 中的敏感数据;此外,容器也可以环境变量的方式使用这些数据。
Secret 可通过命令行或 YAML 创建。比如希望 Secret 中包含如下信息: 用户名 admin 密码 123456
创建 Secret
有四种方法创建 Secret:
1. 通过 --from-literal : kubectl create secret generic mysecret --from-literal=username=admin --from-literal=password=123456
每个 --from-literal 对应一个信息条目。
2. 通过 --from-file : echo -n admin > ./username echo -n 123456 > ./password kubectl create secret generic mysecret --from-file=./username --from-file=./password
每个文件内容对应一个信息条目。
3. 通过 --from-env-file : cat << EOF > env.txt username=admin password=123456 EOF kubectl create secret generic mysecret --from-env-file=env.txt
文件 env.txt 中每行 Key=Value 对应一个信息条目。
4. 通过 YAML 配置文件:
文件中的敏感数据必须是通过 base64 编码后的结果。
执行 kubectl apply 创建 Secret:
下一节我们学习如何使用这些创建好的 Secret。
书籍:
1.《每天5分钟玩转Kubernetes》
https://item.jd.com/26225745440.html
2.《每天5分钟玩转Docker容器技术》
https://item.jd.com/16936307278.html
3.《每天5分钟玩转OpenStack》
https://item.jd.com/12086376.html
云计算
2018-04-16 06:30:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
1.1 Linux进程管理

进程管理是操作系统的最重要的功能之一。有效率的进程管理能保证一个程序平稳而高效地运行。
Linux的进程管理与UNIX的进程管理相似。它包括进程调度、中断处理、信号、进程优先级、上下文切换、进程状态、进度内存等。
大部分操作系统(如Windows、Linux)的任务调度是采用 时间片轮转 的抢占式调度方式。
进程一般由 程序、数据集合和进程控制块 三部分组成
在本节中,我们将描述Linux进程管理的基本原理的实现。它将更好地帮助你理解Linux内核如何处理进程及其对系统性能的影响。
进程具有的特征: 动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的; 并发性:任何进程都可以同其他进程一起并发执行; 独立性:进程是系统进行资源分配和调度的一个独立单位; 结构性:进程由程序、数据和进程控制块三部分组成。
线程 线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间 线程组成: 线程ID 当前指令指针(PC) 寄存器和堆栈。
而进程由内存空间(代码、数据、进程空间、打开的文件)和一个或多个线程组成。
进程与线程的生命周期 1、当线程的数量小于处理器的数量时,线程的并发是真正的并发,不同的线程运行在不同的处理器上。但当线程的数量大于处理器的数量时,线程的并发会受到一些阻碍,此时并不是真正的并发,因为此时至少有一个处理器会运行多个线程。 2、在单个处理器运行多个线程时,并发是一种模拟出来的状态。 3、在早期只有进程的操作系统中,进程有五种状态,创建、就绪、运行、阻塞(等待)、退出。早期的进程相当于现在的只有单个线程的进程,那么现在的多线程也有五种状态,现在的多线程的生命周期与早期进程的生命周期类似。

进程的生命周期
进程在运行过程有三种状态:就绪、运行、阻塞,创建和退出状态描述的是进程的创建过程和退出过程。 创建:进程正在创建,还不能运行。操作系统在创建进程时要进行的工作包括分配和建立进程控制块表项、建立资源表格并分配资源、加载程序并建立地址空间;
就绪:时间片已用完,此线程被强制暂停,等待下一个属于他的时间片到来;
运行:此线程正在执行,正在占用时间片;
阻塞:也叫等待状态,等待某一事件(如IO或另一个线程)执行完;
退出:进程已结束,所以也称结束状态,释放操作系统分配的资源。

线程的生命周期
创建:一个新的线程被创建,等待该线程被调用执行;
就绪:时间片已用完,此线程被强制暂停,等待下一个属于他的时间片到来;
运行:此线程正在执行,正在占用时间片;
阻塞:也叫等待状态,等待某一事件(如IO或另一个线程)执行完;
退出:一个线程完成任务或者其他终止条件发生,该线程终止进入退出状态,退出状态释放该线程所分配的资源。
进程与线程的区别 1、线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位; 2、一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线; 3、进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号),某进程内的线程在其它进程不可见; 4、调度和切换:线程上下文切换比进程上下文切换要快得多。
总之,线程和进程都是一种抽象的概念,线程是一种比进程更小的抽象,线程和进程都可用于实现并发。 在早期的操作系统中并没有线程的概念,进程是能拥有资源和独立运行的最小单位,也是程序执行的最小单位。它相当于一个进程里只有一个线程,进程本身就是线程。所以线程有时被称为轻量级进程

Linux内存布局
在linux中,每一个 进程都被抽象为task_struct结构体 ,称为进程描述符,存储着进程
各方面的信息;例如打开的文件,信号以及内存等等;然后task_struct的一个属性mm_struct管理着进程的所有虚拟内存,称为内存描述符。在mm_struct结构体中,存储着进程各个内存段的开始以及结尾,如上图所示;这个进程使用的物理内存,即常驻内存RSS页数,这个内存使用的虚拟地址空间VSZ页数,还有这个进程虚拟内存区域集合和页表。

从上面这个图可以看出,进程是有代码段Text segment,数据段(已初始化的全局,静态变量),BSS段(未初始化的全局,静态变量),堆,内存映射区以及栈;

每一块虚拟内存区(VMA)都是由一块连续的虚拟地址组成,这些地址从不覆盖。一个vm_area_struct实例描述了一块内存区域,包括这块内存区域的开始以及结尾地址;flags标志决定了这块内存的访问权限和行为;vm_file决定这块内存是由哪个文件映射的,如果没有文件映射,则这块内存为匿名的(anonymous)。上述图中提到的每个内存段,都对应于一个vm_area_struct结构。如下图所示
上图即为/bin/gonzo进程的内存布局。程序的二进制文件映射到代码段和数据段,代码段为只读只执行,不可更改;全局以及静态的未初始化的变量映射到BSS段,为匿名映射,堆和栈也是匿名映射,因为没有相应的文件映射;内存映射区可以映射共享库,映射文件以及匿名映射,所以这块内存段可以是文件映射也可以是匿名映射。而且不同的文件,映射到不同的vm_area_struct区。

这些vm_area_struct集合存储在mm_struct中的一个单向链表和红黑树中;当输出/proc/pid/maps文件时,只需要遍历这个链表即可。红黑树主要是为了快速定位到某一个内存块,红黑树的根存储在mm_rb域。

之前介绍过,线性地址需要通过页表才能转换为物理地址。每个进程的内存描述符也保存了这个进程页表指针pgd,每一块虚拟内存页都和页表的某一项对应。

虚拟内存是不存储任何数据的,它只是将地址空间映射到物理内存。物理内存有内核伙伴系统分配,如果一块物理内存没有被映射,就可以被伙伴系统分配给虚拟内存。刚分配的物理内存叶框可能是匿名的,存储进程数据,也可能是也缓存,存储文件或块设备的数据。一块虚拟内存vm_area_struct块是由连续的虚拟内存页组成的,而这些虚拟内存块映射的物理内存却不一定连续,如下图所示
如上图所示,有三个页映射到物理内存,还有两个页没有映射,所以常驻内存RSS为12kb,而虚拟内存大小为20kb。对于有映射到物理内存的三个页的页表项PTE的Present标志设为1,而两个没有映射物理内存的虚拟内存页表项的Present位清除。所以这时访问那两块内存,则会导致异常缺页。

vma就像应用程序和内核的一个契约。当应用程序申请内存或者文件映射时,内核先响应这个请求,分配或更新虚拟内存;但是这些虚拟内存并没有映射到真实的物理内存。而是等到内存访问产生一个内存异常缺页时才真正映射物理内存。即当访问没有映射的虚拟内存时,由于页表项的Present位没有被设置,所以此时会产生一个缺页异常。vma记录和页表项两个在解决内存缺页,释放内存以及内存swap out都起着重要的作用。下面图展示了上述情况:
1、一开始堆中只有8kb的内存,而且都已经映射到物理内存;
2、当调用brk()函数扩展堆时,新的页是没有映射到物理内存的,
3、当处理器需要访问一个地址,而且这个地址在上述刚分配的虚拟内存中,这时产生一个缺页异常;
4、这时进程向伙伴系统申请一页的物理内存,映射到那块虚拟内存上,并添加页表项,设置Present位.

自此,这个内存管理暂时就说到这。总结下:

1、Linux进程的内存布局的每个段都是有一个vm_area_struct,而这个实例是由连续的虚拟内存地址组成;
2、当请求内存时,先是扩展vm_area_struct或者新分配一个vm_area_struct,但是并不映射物理内存,只有等到访问这块内存时,产生缺页异常,内核才分配物理内存
1.1.1 什么是进程?
一个进程是一个运行在处理器的程序的一个实例。该进程使用Linux内核能够处理的任何资源来完成它的任务。
所有运行在Linux操作系统中的进程都被task_struct结构管理,该结构同时被叫作进程描述。一个进程描述包含一个运行进程所有的必要信息,例如进程标识、进程属性和构建进程的资源。如果你了解该进程构造,你就能理解对于进程的运行和性能来说,什么是重要的。图1-2展示了进程结构相关的进程信息概述。
图1-2 task_struct结构体
1.1.2 进程的生命周期
每一个进程都有其生命周期,例如创建、运行、终止和消除。这些阶段会在系统启动和运行中重复无数次。因此,进程的生命周期对于其性能的分析是非常重要的。
图1-3展示了经典的进程生命周期。
图1-3 经典的进程生命周期
当一个进程创建一个新的进程,进程的创建进程(父进程)调用 一个fork()系统调用。当fork()系统调用被调用,它得到该新创建进程(子进程)的进程描述并调用一个新的进程id。它复制该值到父进程进程描述到子进程中。此时整个的父进程的地址空间是没有被复制的;父子进程共享相同的地址空间。
exec()系统调用复制新的程序到子进程的地址空间。因为父子进程共享地址空间,写入一个新的程序的数据会引起一个分页错误。在这种情况下,内存会分配新的物理内存页给子进程。
这个推迟的操作叫作写时复制。子进程通常运行他们自己的程序而不是与父进程运行相同的程序。这个操作避免了不必要的开销,因为复制整个地址空间是一个非常缓慢和效率低下的操作,它需要使用大量的处理器时间和资源。
当程序已经执行完成,子进程通过调用exit()系统调用终止。exit()系统调用释放进程大部分的数据并通过发送一个信号通知其父进程。此时,子进程是一个被叫作僵尸进程的进程(参阅page 7的“Zombie processes”)。
子进程不会被完全移除直到其父进程知道其子进程的调用wait()系统调用而终止。当父进程被通知子进程终止,它移除子进程的所有数据结构并释放它的进程描述。
1.1.3 线程
一个线程是一个单独的进程生成的一个执行单元。它与其他的线程并行地运行在同一个进程中。各个线程可以共享进程的资源,例如内存、地址空间、打开的文件等等。它们能访问相同的程序数据集。线程也被叫作轻量级的进程(Light Weight Process,LWP)。因为它们共享资源,所以每个线程不应该在同一时间改变它们共享的资源。互斥的实现、锁、序列化等是用户程序的责任。
从性能的角度来说,创建线程的开销比创建进程少,因数创建一个线程时不需要复制资源。另一方面,进程和线程拥在调度算法上有相似的特性。内核以相似的方式处理它们。
图1-4 进程和线程
在现在的Linux实现中,线程支持UNIX的可移植操作系统接口(POSIX)标准库。在Linux操作系统中有几种可用的线程实现。以下是广泛使用的线程库:
LinuxThreads
LinuxThreads自从Linux内核2.0起就已经被作为默认的线程实现。LinuxThreads的一些实现并不符合POSIX标准。Native POSIX Thread Library(NPTL)正在取代LinuxThreads。LinuxThreads在将来的Linux企业发行版中将不被支持。
Native POSIX Thread Libary(NPTL)
NPTL最初是由红帽公司开发的。NPTL与POSIX更加兼容。通过Linux内核2.6的高级特性,例如,新的clone()系统调用、信号处理的实现等等,它具有比LinuxThreads更高的性能和伸缩性。
NPTL与LinuxThreads有一些不兼容。一个依赖于LinuxThreads的应用可能不能在NPTL实现中工作。
Next Generation POSIX Thread(NGPT)
NGPT是一个IBM开发的POSIX线程库。现在处于维护阶段并且在未来也没有开发计划。
使用LD_ASSUME_KERNEL环境变量,你可以选择在应用中使用哪一个线程库。
1.1.4 进程优先级和nice值
进程优先级是一个数值,它通过动态的优先级和静态的优先级来决定进程被CPU处理的顺序。一个拥有更高进程优先级的进程拥有更大的机率得到处理器的处理。
内核根据进程的行为和特性使用试探算法,动态地调整调高或调低动态优先级。一个用户进程可以通过使用进程的nice值间接改变静态优先级。一个拥有更高静态优先级的进程将会拥有更长的时间片(进程能在处理上运行多长时间)。
Linux支持从19(最低优先级)到-20(最高优先级)的nice值。默认值为0。把程序的nice值修改为负数(使进程的优先级更高),需要以root身份登陆或使用su命令以root身份执行。
1.1.5 上下文切换
在进程运行过程中,进程的运行信息被保存于处理器的寄存器和它的缓存中。正在执行的进程加载到寄存器中的数据集被称为上下文。为了切换进程,运行中进程的上下文将会被保存,接下来的运行进程的上下文将被被恢复到寄存器中。进程描述和内核模式堆栈的区域将会用来保存上下文。这个切换被称为上下文切换。过多的上下文切换是不受欢迎的,因为处理器每次都必须清空刷新寄存器和缓存,为新的进程制造空间。它可能会引起性能问题。
图1-5 说明了上下文切换如何工作。
图1-5 上下文切换
1.1.6 中断处理
中断处理是优先级最高的任务之一。中断通常由I/O设备产生,例如网络接口卡、键盘、磁盘控制器、串行适配器等等。中断处理器通过一个事件通知内核(例如,键盘输入、以太网帧到达等等)。它让内核中断进程的执行,并尽可能快地执行中断处理,因为一些设备需要快速的响应。它是系统稳定的关键。当一个中断信号到达内核,内核必须切换当前的进程到一个新的中断处理进程。这意味着中断引起了上下文切换,因此大量的中断将会引起性能的下降。
在Linux的实现中,有两种类型的中断。硬中断是由请求响应的设备发出的(磁盘I/O中断、网络适配器中断、键盘中断、鼠标中断)。软中断被用于处理可以延迟的任务(TCP/IP操作,SCSI协议操作等等)。你可以在/proc/interrupts文件中查看硬中断的相关信息。
在多处理器的环境中,中断被每一个处理器处理。绑定中断到单个的物理处理中能提高系统的性能。更多的细节,请参阅4.4.2,“CPU的中断处理亲和力”。
1.1.7 进程状态
每一个进程拥有自己的状态,状态表示了进程当前在发生什么。
在进程的执行期间进程的状态会发生改变。一些进程的状态如下:
TASK_RUNNING
在此状态下,表示进程正在CPU中运行或在队列中等待运行(运行队列)。
TASK_STOPPED
在此状态下的进程被某些信号(如SIGINT,SIGSTOP)暂停。进程正在等待通过一个信号恢复运行,例如SIGCONT。
TASK_INTERRUPTIBLE
在此状态下,进程被暂停并等待一个某些条件状态的到达。如果一个进程处于TASK_INTERRUPTIBLE状态并接收到一个停止的信号,进程的状态将会被改变并中断操作。一个典型的TASK_INTERRUPTIBLE状态的进程的例子是一个进程等待键盘中断。
TASK_UNINTERRUPTIBLE
与TASK_INTERRUPTIBLE相似。当一个进程处于TASK_UNINTERRUPTIBLE状态可以被中断,向处于TASK_UNINTERRUPTIBLE状态的进程发送一个信号不会发生任何操作。一个TASK_UNINTERRUPTIBLE进程的典型的例子是等待磁盘I/O操作。
TASK_ZOMBIE
当一个进程调用exit()系统调用退出后,它的父进程应该知道该进程的终止。处于TASK_ZOMBIE状态的进程会等待其父进程通知其释放所有的数据结构。
图1-6 进程状态
僵尸进程
当一个进程接收到一个信号而终止,它在结束自己之前,通常需要一些时间来结束所有的任务(例如关闭打开的文件)。在这个通常非常短暂的时间内,该进程就是一个僵尸进程。
进程已经完成所有的关闭任务后,它会向父进程报告其即将终止。有些时候,一个僵尸进程不能把自己终止,这将会引导它的状态显示为z(zombie)。
使用kill命令来关闭这样的一个进程是不可能的,因为该进程已经被认为已经死掉了。如果你不能清除僵尸进程,你可以结束其父进程,然后僵尸进程也随之消失。但是,如果父进程为init进程,你不能结束它。init进程是一个非常重要的进程,因此可能需要重启系统来清除僵尸进程。
1.1.8 进程内存段
进程使用其自身的内存区域来执行工作。工作的变化根据情况和进程的使用而决定。进程可以拥有不同的工作量特性和不同的数据大小需求。进程必须处理各种数据大小。为了满足需求,Linux内核为每个进程使用动态申请内存的机制。进程内存分配的数据结构如图1-7所示。
图1-7 进程地址空间
进程内存区由以下几部分组成:
Text段
该区域用于存储运行代码。
Data段
数据段包括三个区域。
– Data:该区域存储已被初始化的数据,如静态变量。
– BSS:该区域存储初始化为0的数据。数据被初始化为0。
– Heap:该区域用于根据需求使用malloc()动态申请的内存。堆向高地址方向增长。
Stack段
该区域用于存储局部变量、函数参数和返回函数的地址。栈向低地址方向增长。
用户进程的地址空间内存分布可以使用pmap命令来查看。你可以使用ps命令来查看内存段的大小。可以参阅2.3.10的“pmap”,“ps和pstree”。
1.1.9 Linux CPU调度
任何的计算机的基本功能都非常简单,就是计算。为了能够计算,它意味着必须管理计算资源或处理器和计算任务,也就是我们所知道的线程或进程。感谢Ingo Molnar的巨大贡献,Linux内核使用一个O(1)的算法代替以前的O(n)的CPU调度算法。O(1)指的是一种静态的算法,意味着选择一个进程并执行所花费的时间是一个常数,不管进程的数量的大小。
新的调度算法的扩展性非常好,不管进程的数量或者处理器的数量是多少,系统的开销都是非常少的。该算法使用两个进程优先级数组:
active(活动的)
expired(过期的)
调度器根据进程的优先级和优先拦截率为进程分配时间片,然后进程以优先级顺序放置到active数组内。当进程时间片耗尽,进程申请一个新的时间片并放置到expired数组内。当active数组中的所有进程的时间片耗尽,这两个数组进行切换,重新运行该算法。对于一般的交互式进程(相对于实时进程),拥有高优先级的进程通常比低优先级的进程得到更长的时间片和更多的计算时间,但这并不表示低优先级的进程会被完全忽略(饿死)。该算法的优势是为拥有大量线程和进程并拥有多处理器的企业级环境提升Linux内核的扩展性。该O(1)的新CPU调度器是为内存2.6设计的,但是现在已经移植到2.4系列中。图1-8说明了Linux CPU如何调度工作。
图1-8 Linux内核2.6 O(1)调度器
新调度器的另一个显著改进是支持非一致性内存架构(NUMA)和对称多线程处理器,例如Intel超线程技术。
改进后的NUMA支持确保只有某个节点过载时,负载平衡才会跨越某个NUMA节点。这个机制确保了在NUMA系统相对比较缓慢的扩展链接流量的最小化。尽管每个调度节拍时负载平衡会遍历调度域群组中的处理器,但只有在节点过载并请求负载平衡时,负载才会跨越调度域转移。
图1-9 O(1)CPU调度器结构
僵尸进程的产生
僵尸进程就是已经结束的进程,但是还没有从进程表中删除。僵尸进程太多会导致进程表里面条目满了,进而导致系统崩溃,倒是不占用系统资源。
在进程的状态中,僵尸进程是非常特殊的一种,它已经放弃了几乎所有的内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个为位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占用任何内存空间,它需要它的父进程来给它收尸,如果父进程没安装SIGCHLD信号处理函数调用wait或waitpid()等待子进程结束,又没有显示的忽略该信号,那么它就一直处于僵尸状态。如果父进程结束了,那么init进程会自动接手这个子进程,为它收尸,他还是能够被清除的。但是如果父进程是一个循环,不会结束,那么子进程就一直处于僵尸状态。
僵尸进程产生的原因:
每个Linux进程在进程表中都有一个进入点(Entry),核心程序在执行该进程时使用到的一切信息都存储在进入点。当使用ps命令查看系统中的进程信息时,看到的就是进程表中的相关数据。
当fork系统调用建立一个新的进程以后,核心进程就会在进程表中给这个新进程分配一个进入点,然后将相关信息存储在该进入点所对应的进程表中,这些信息中有一项是父进程的识别码。
当这个进程走完了自己的生命周期后,它会执行exit()系统调用,此时原来进程表中的数据会被该进程的退出码、执行时所用的CPU时间等数据所取代,这些数据会一直保留到系统将它传递给它的父进程为止。由此可见,僵尸进程的出现时间实在子程序终止后,但是父进程尚未读取这些数据之前。
如何避免僵尸进程
1、父进程通过wait和waitpid等函数等待子进程结束,这会导致父进程挂起
2、如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler,因为子进程结束后,父进程会收到该信号,可以在handler中调用wait回收。
3、如果父进程不关心子进程什么时候结束,那么可以用“singal(SIGCHLD),SIG_IGN”通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收,并不再给父进程发送信号。
4、还有一些技巧,就是fork()两次,父进程fork一个子进程,然后继续工作,子进程fork一个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收,不过子进程回收还要自己做。
进程 PK 线程
我们先打个比方,多线程是十字路口多线程是平面交通系统,造价低,但是红绿灯多,老堵车,而多进程是则是立交桥,虽然造价高,上下坡多耗油,但是不堵车。这是一个抽象的概念。相信大家看完会有这种感觉。
进程和线程是两个相对的概念,通常来说,一个进程可以定义程序的一个实例(Instance)。在Win32中,进程并不执行什么,它只是占据应用程序所使用的地址空间。为了让进程完成一定的工作,进程必须至少占有一个线程,正是这个线程负责包含进程地址空间中的代码。
实际上,一个进程可以包含几个线程,它们可以同时执行进程地址空间中的代码。为了做到这一点,每个线程有自己的一组CPU寄存器和堆栈。每个进程中至少有 一个线程在执行其地址空间中的代码。如果没有线程执行进程地址空间中的代码,进程也就没有继续存在的理由,系统将自动清除进程及其地址空间。
多线程的实现原理
创建一个进程时,它的第一个线程称为主线程(Primary thread),由系统自动生成。然后可以由这个主线程生成额外的线程,而这些线程,又可以生成更多的线程。在运行一个多线程的程序时,从表面上看,这些线程似乎在同时运行。而实际情况并非如此,为了运行所有的这些线程,操作系统为每个独立线程安排一些CPU时间。
单CPU操作系统以时间片轮转方式向线程提供时间片(Quantum),每个线程在使用完时间片后交出控制,系统再将CPU时间片分配给下一个线程。由于每个时间片足够的短,这样就给人一种假象,好像这些线程在同时运行。创建额外线程的唯一目的就是尽可能地利用CPU时间。
多线程的问题
线程很有用,但当使用线程时,可能会在解决老问题的同时产生新问题。例如要开发一个字处理程序,并想让打印功能作为单独的线程自己执行。这听起来是很好的主意,因为在打印时,用户可立即返回,开始编辑文档。
但这样一来,在该文档被打印时文档中的数据就有可能被修改,打印的结果就不再是所期望的内容。也许最好不要把打印功能放在单独的线程中,不过如果一定要用多线程的话,也可以考虑用下面的方法解决:第一种方法是锁定正在打印的文档,让用户编辑其他的文档,这样在结束打印之前,该文档不会作任何修改;另一个方法可能更有效一些,即可以把该文档拷贝到一个临时文件中,打印这个临时文件的内容,同时允许用户对原来的文档进行修改。
当包含文档的临时文件打印完成时,再删去这个临时文件。通过上面的分析可以看出,多线程在帮助解决问题的同时也可能带来新问题。因此有必要弄清楚,什么时候需要创建多线程,什么时候不需要多线程。总的来说,多线程往往用于在前台操作的同时还需要进行后台的计算或逻辑判断的情况。 线程的分类
在MFC中,线程被分为两类,即 工作线程和用户界面线程 。如果一个线程只完成后台计算,不需要和用户交互,那么可以使用工作线程;如果需要创建一个处理用户界面的线程,则应使用用户界面线程。这两者的主要区别在于,MFC框架会给用户界面线程增加一个消息循环,这样用户界面线程就可以处理自己消息队列中的消息。
这样看来,如果需要在后台作一些简单的计算(如对电子表格的重算),则首先应考虑使用工作线程,而当 后台线程需要处理比较复杂的任务,确切地说,当后台线程的执行过程会随着实际情况的不同而改变时,就应该使用用户界面线程,以便能对不同的消息作出响应。 线程的优先级
当系统需要同时执行多个进程或多个线程时,有时会需要指定线程的优先级。线程的优先级一般是指这个线程的基优先级,即线程相对于本进程的相对优先级和包含此线程的进程的优先级的结合。
操作系统以优先级为基础安排所有的活动线程,系统的每一个线程都被分配了一个优先级,优先级的范围从0到31。运行时,系统简单地给第一个优先级为31的线程分配CPU时间,在该线程的时间片结束后,系统给下一个优先级为31的线程分配CPU时间。当没有优先级为31的线程时,系统将开始给优先级为30的线程分配CPU时间,以此类推。
除了程序员在程序中改变线程的优先级外,有时程序在执行过程中系统也会自动地动态改变线程的优先级,这是为了保证系统对终端用户的高度响应性。比如用户按了键盘上的某个键时,系统就会临时将处理WM_KEYDOWN消息的线程的优先级提高2到3。CPU按一个完整的时间片执行线程,当时间片执行完毕后,系统将该线程的优先级减1。 线程的同步
在使用多线程编程时,还有一个非常重要的问题就是线程同步。所谓线程同步是指线程之间在相互通信时避免破坏各自数据的能力。同步问题是由前面说到的Win32系统的CPU时间片分配方式引起的。
虽然在某一时刻,只有一个线程占用CPU(单CPU时)时间,但是没有办法知道在什么时候,在什么地方线程被打断,这样如何保证线程之间不破坏彼此的数据就显得格外重要。在MFC中,可以使用4个同步对象来保证多线程同时运行。它们分别是临界区对象(CCriticalSection)、互斥量对象(CMutex)、信号量对象(CS emaphore)和事件对象(CEvent)。
在这些对象中,临界区对象使用起来最简单,它的缺点是只能同步同一个进程中的线程。另外,还有一种基本的方法,本文称为线性化方法,即在编程过程中对一定数据的写操作都在一个线程中完成。这样,由于同一线程中的代码总是按顺序执行的,就不可能出现同时改写数据的情况。
总结:
在线程中(相对与进程而言),线程是一个更加接近执行体的概念,它可以与同进程的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。这两者都可以提高程序的并发度,提高程序运行的效率和响应的时间。
线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源管理和保护;而进程正好相反。根本的区别就一点:用多进程每个进程有自己的地址空间,线程则共享地址空间,在速度方面:线程产生的速度快,线程间的通讯快,切换快等,因为他们在同一地址空间内。
在资源利用率方面:线程的资源率比较好也是因为他们在同一地址空间内。 在同步方面:线程使用公共变量/内存时需要使用同步机制,因为他们在同一地址空间内进程中:子进程是父进程的复制品,子进程获得父进程数据空间、堆和栈的复制品。

进程内缓存
什么是进程内缓存?
除了常见的redis/memcache等进程外缓存服务,缓存还有一种常见的玩法,进程内缓存。
答 :将一些数据缓存在站点,或者服务的进程内,这就是进程内缓存。
进程内缓存的实现载体,最简单的,可以是一个 带锁的Map 。又或者,可以使用 第三方库 ,例如leveldb。

进程内缓存能存储啥?
答 :redis/memcache等进程外缓存服务能存什么,进程内缓存就能存什么。

进程内缓存有什么好处?
答 : 与没有缓存相比 ,进程内缓存的好处是,数据读取不再需要访问后端,例如数据库。
如上图,整个访问流程要经过1,2,3,4四个步骤。

如果引入进程内缓存,
如上图,整个访问流程只要经过1,2两个步骤。

与进程外缓存相比 (例如redis/memcache),进程内缓存省去了网络开销,所以一来节省了内网带宽,二来响应时延会更低。

进程内缓存有什么缺点?
答 :统一缓存服务虽然多一次网络交互,但仍是统一存储。
如上图,站点和服务中的多个节点访问统一的缓存服务,数据统一存储,容易保证数据的一致性。

而进程内缓存,如上图,如果数据缓存在站点和服务的多个节点内,数据存了多份,一致性比较难保障。

如何保证进程内缓存的数据一致性?
答 :保障进程内缓存一致性,有几种方案。

第一种方案 ,可以通过单节点通知其他节点。如上图:写请求发生在server1,在修改完自己内存数据与数据库中的数据之后,可以主动通知其他server节点,也修改内存的数据。

这种方案的 缺点 是:同一功能的一个集群的多个节点,相互耦合在一起,特别是节点较多时,网状连接关系极其复杂。

第二种方案 ,可以通过MQ通知其他节点。如上图,写请求发生在server1,在修改完自己内存数据与数据库中的数据之后,给MQ发布数据变化通知,其他server节点订阅MQ消息,也修改内存数据。

这种方案虽然解除了节点之间的耦合,但引入了MQ,使得系统更加复杂。

前两种方案,节点数量越多,数据冗余份数越多,数据同时更新的原子性越难保证,一致性也就越难保证。

第三种方案 ,为了避免耦合,降低复杂性,干脆放弃了“实时一致性”,每个节点启动一个timer,定时从后端拉取最新的数据,更新内存缓存。在有节点更新后端数据,而其他节点通过timer更新数据之间,会读到脏数据。

为什么不能频繁使用进程内缓存?
答 : 分层架构设计,有一条准则 :站点层、服务层要做到无数据无状态,这样才能任意的加节点水平扩展,数据和状态尽量存储到后端的数据存储服务,例如数据库服务或者缓存服务。

可以看到,站点与服务的 进程内缓存 ,实际上违背了分层架构设计的无状态准则,故一般不推荐使用。

什么时候可以使用进程内缓存?
答 :以下情况,可以考虑使用进程内缓存。
情况一 ,只读数据,可以考虑在进程启动时加载到内存。
画外音:此时也可以把数据加载到redis / memcache,进程外缓存服务也能解决这类问题。

情况二 ,极其高并发的,如果透传后端压力极大的场景,可以考虑使用进程内缓存。
例如,秒杀业务,并发量极高,需要站点层挡住流量,可以使用内存缓存。

情况三 ,一定程度上允许数据不一致业务。
例如,有一些计数场景,运营场景,页面对数据一致性要求较低,可以考虑使用进程内页面缓存。

末了,再次强调,进程内缓存的适用场景并不如redis/memcache广泛,不要为了炫技而使用。

更多的时候,还是老老实实使用redis/mc吧。

参考链接 :
操作系统之进程与线程 : http://tanqingbo.com/2016/04/03/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E4%B9%8B%E8%BF%9B%E7%A8%8B%E4%B8%8E%E7%BA%BF%E7%A8%8B/

《 缓存架构,到底设计些什么? 》:https://mp.weixin.qq.com/s?__biz=MjM5ODYxMDA5OQ==&mid=2651961264&idx=1&sn=00b2ace48ad7f3630799c564b27af69a&chksm=bd2d026c8a5a8b7a8056de54768b19e24f781b1f6df1686aeee2d5174d5030967f3fb4375a1b&scene=21#wechat_redirect
选redis还是memcache,源码怎么说?:https://mp.weixin.qq.com/s?__biz=MjM5ODYxMDA5OQ==&mid=2651961272&idx=1&sn=79ad515b013b0ffc33324db86ba0f834&chksm=bd2d02648a5a8b728db094312f55574ec521b30e3de8aacf1d2d948a3ac24dbf30e835089fa7&scene=21#wechat_redirect
“选redis还是memcache”,面试官究竟想考察啥?:https://mp.weixin.qq.com/s?__biz=MjM5ODYxMDA5OQ==&mid=2651961269&idx=1&sn=ea447397423a2ad9d9f44ad51f0bed5e&chksm=bd2d02698a5a8b7f966f77c0283124e7d7dee42cc604b418b57ba4ff15e583fe2873a356dc31&scene=21#wechat_redirect

原文地址:http://embed.21ic.com/software/linuxos/201805/56190.html
转载链接: 链接:blog.csdn.net/ljianhui/article/details/46718835
云计算
2018-04-14 16:05:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
1.iptables filter表案例
一个需求:
针对filter表,预设策略INPUT链DROP,其他2个链ACCEPT,然后针对192.168.188.0/24开通22端口,对所有网段开放80端口,对所有网段开放21端口
写一个脚本就可以了:
#!/bin/bash ---定义解释器
IPT="/usr/sbin/iptables'"------定义变量名
$IPT -F ----清空所有规则
$IPT -P INPUT DROP ----把INPUT链收到的数据包扔掉
$IPT -P FORWARD ACCEPT ------接受FORWARD的数据包
$IPT -P OUTPUT ACCEPT ---------接受发送出去的所有数据包
$IPT -A INPUT -p tcp -s 192.168.188.0/24 --dport 22 -j ACCEPT -----针对192.168.188.0/24开通22端口,
$IPT -A INPUT -p tcp --dport 80 -j ACCEPT ------开放80端口
$IPT -A INPUT -p tcp --dport 21 -j ACCEPT --------开放21端口
运行结果如下:
关于icmp包有一个比较常用的应用
iptables -I INPUT -p icmp --icmp-type 8 -j DROP
这条命令,作用是禁止他人ping你的机器,而你可以ping通其他机器。
iptables nat表应用






解析:-F 清空规则。
-nvL 查看规则
-Z 清零
-t 指定表的名字。
-A 把规则放最后面
-I 把规则放最前面
INPUT 进去的包
-s 指定ip地址
-p 指定协议
-sport 指定源端口
-dport 指定目标端口
-d 指定目标地址
-j 跟行为动作(DROP,ACCEPT,)
- D 删除
-P 默认的

把22,80,21端口放行,但22端口指定ip段访问 ,其他都拒绝
实例:
进入iptables脚本,进行编辑。写shell脚步时写绝对路径。把网段192的,端口22的,放行使用命令w,查看被放行的网关

ping外网可以ping通,ping内网ping不通
恢复设置的iptables,使用命令:services iptables restart,和命令:iptables -nvL区查看。
然后再加上规则。命令:iptables -I INPUT -p icmp --icmp-type 8 -j DROP
2. iptables nat表应用
A机器2块网卡分别是外网和内网,B机器只有内网
需求1:可以让B机器联网外网
首先我们做个测试:
步骤1:在虚拟机。A机器添加一个内网网卡,B机器也添加内网网卡。 网卡模式并设置为LAN区段,自定义名字一样。
添加网卡:先关闭虚拟机,点击编辑虚拟机设置,选中“网络适配器”,点击添加,最后点击完成。

添加区段
名字可以自己设置,点击“添加按钮”,最最后点击”确定“


步骤2:查询是否添加成功
步骤3:添加IP地址,(重启就没了的这IP,要想永久生效,必须拷贝一个网卡配置信息,修改name,ip...等等)
临时添加A机器内网IP地址:ifconfig ens34 192.168.11.1/24
临时添加B机器内网IP地址:ifconfig ens34 192.168.11.2/24
步骤4:检查是否能ping通
步骤5:要想实现nat应用,在A机器必须打开路由转发功能,我们cat下/proc/sys/net/ipv4/ip_forward这个配置文件,默认是0,现在已打开,——打开方法 echo ' 1'>/proc/sys/net/ipv4/ip_forward
步骤6:在A机器nat表添加规则
iptables -t nat -A POSTROUTING -s 192.168.11.0/24 -o ens33 -j MASQUERADE
步骤7:设置B机器网关
查看网关命令 route -n
设置:route add default gw 192.168.11.1
步骤8:ping A机器的网关和ping DNS 119.29.29.29测试
这样就完成了在A机器实现一个当路由器的角色,使B机器联系外网

需求2:window机器连接B机器,因为B机器在外面是ping不通,所以我们需要通过A机器做一个端口映射到B来远程连接
步骤1:打开A机器的路由转发 echo '1' /proc/sys/net/ipv4/ip_forward
步骤2:A机器上执行添加2条规则:
iptables -t nat -A PREOUTING -d 192.168.0.170 -p tcp --dport 1122 -j DNAT --to 192.168.11.2:22
iptables -t nat -A POSTROUTING -s 192.168.11.2 -j SNAT --to 192.168.0.170
步骤3:B上设置网关为 192.168.11.1

iptables nat 服务器(路由器)配置
客户端 nat服务器 web服务器
eth0:192.168.200.156 eth0:192.168.200.158 eth0:192.168.100.254
网关:192.168.200.158 eth1:192.168.100.10

所有Ip都在一个交换机下面
iptables 配置如下:
#清除默认规则 iptables -F iptables -X iptables -Z iptables -t nat -F iptables -t nat -X iptables -t nat -Z #默认操作 iptables -P INPUT DROP iptables -P OUTPUT ACCEPT iptables -P FORWARD DROP #INPUT基本规则 iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT iptables -A INPUT -m state --state INVALID -j DROP #FORWARD基本规则 iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT iptables -A FORWARD -m state --state INVALID -j DROP #放行所有通过lo的数据包 iptables -A INPUT -i lo -j ACCEPT #icmp iptables -A INPUT -p icmp --icmp-type any -s 192.168.200.0/24 -j ACCEPT iptables -A INPUT -p icmp --icmp-type any -s 192.168.100/24 -j ACCEPT #sshd iptables -A INPUT -s 192.168.200.0/24 -i eth1 -p tcp --dport 22 -j ACCEPT iptables -A INPUT -s 192.168.200.0/24 -i eth1 -p udp --dport 22 -j ACCEPT #nginx or httpd iptables -A INPUT -s 192.168.200.0/24 -i eth1 -p tcp --dport 80 -j ACCEPT iptables -A INPUT -s 192.168.200.0/24 -i eth1 -p udp --dport 80 -j ACCEPT #修改数据包的来源ip iptables -t nat -A POSTROUTING -s 192.168.200.0/24 -j SNAT --to-source 192.168.100.10 #放行的ip iptables -A FORWARD -s 192.168.200.156 -j ACCEPT service iptables save service iptables restart #iptables-save 查看规则比iptables -L更好读懂
分析:

1)客户端访问web,mangle表不考虑,首先通过PREROUTING链表,对于192.168.200.156发送的数据包来说,没有对PREROUTING链表的规则,则放行,经过路由判断,目的地址为
192.168.100.254,则通往B,此时会经过FORWARD链表,由下面iptables配置知,这个数据包将被放行,再经过POSTROUTING链表,可知,这个数据包的来源将被改为192.168.100.10,然后被发送出到web
2)web通过客户端发来的数据包,分析到这个数据包的来源为192.168.100.10,则web回应一个数据包给nat服务器,首先经过PREROUTING链表,web知道了192.168.100.10这个ip地址就能将数据包发送到客户端手中只需要 iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT就行,具体原因是怎么将目的地址192.168.100.10改为客户端的ip地址的,估计是nat服务器上有端口映射等原因,不是很清楚

iptables四个表五条链

其实关于iptables的使用网上的资料和教程也比较多,主要是要理解其中的路由前和路由后每个表和链所处的位置和作用,明白了也就简单了,以下是我转载的觉得写的比较详细的一篇博客,有时间我将写一篇关于这些表和链的实质性的配置例子。

一、netfilter和iptables说明:
1、 netfilter/iptables IP 信息包过滤系统是一种功能强大的工具,可用于添加、编辑和除去规则,这些规则是在做信息包过滤决定时,防火墙所遵循和组成的规则。这些规则存储在专用的信息包过滤表中,而这些表集成在 Linux 内核中。在信息包过滤表中,规则被分组放在我们所谓的链(chain)中。
虽然 netfilter/iptables IP 信息包过滤系统被称为单个实体,但它实际上由两个组件 netfilter 和 iptables 组成。
(1). netfilter 组件也称为内核空间(kernelspace),是内核的一部分,由一些信息包过滤表组成,这些表包含内核用来控制信息包过滤处理的规则集。
(2). iptables 组件是一种工具,也称为用户空间(userspace),它使插入、修改和除去信息包过滤表中的规则变得容易。
iptables包含4个表,5个链。其中表是按照对数据包的操作区分的,链是按照不同的Hook点来区分的,表和链实际上是netfilter的两个维度。
2、 4个表:filter,nat,mangle,raw,默认表是filter(没有指定表的时候就是filter表)。表的处理优先级:raw>mangle>nat>filter。
filter:一般的过滤功能
nat:用于nat功能(端口映射,地址映射等)
mangle:用于对特定数据包的修改
raw:有限级最高,设置raw时一般是为了不再让iptables做数据包的链接跟踪处理,提高性能
RAW 表只使用在PREROUTING链和OUTPUT链上,因为优先级最高,从而可以对收到的数据包在连接跟踪前进行处理。一但用户使用了RAW表,在某个链 上,RAW表处理完后,将跳过NAT表和 ip_conntrack处理,即不再做地址转换和数据包的链接跟踪处理了.
RAW表可以应用在那些不需要做nat的情况下,以提高性能。如大量访问的web服务器,可以让80端口不再让iptables做数据包的链接跟踪处理,以提高用户的访问速度。
3、 5个链:PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTING。
PREROUTING:数据包进入路由表之前
INPUT:通过路由表后目的地为本机
FORWARD:通过路由表后,目的地不为本机
OUTPUT:由本机产生,向外转发
POSTROUTIONG:发送到网卡接口之前。如下图:




iptables中表和链的对应关系如下:



二、iptables的数据包的流程是怎样的?
一个数据包到达时,是怎么依次穿过各个链和表的(图)。


基本步骤如下:
1. 数据包到达网络接口,比如 eth0。
2. 进入 raw 表的 PREROUTING 链,这个链的作用是赶在连接跟踪之前处理数据包。
3. 如果进行了连接跟踪,在此处理。
4. 进入 mangle 表的 PREROUTING 链,在此可以修改数据包,比如 TOS 等。
5. 进入 nat 表的 PREROUTING 链,可以在此做DNAT,但不要做过滤。
6. 决定路由,看是交给本地主机还是转发给其它主机。
到了这里我们就得分两种不同的情况进行讨论了,一种情况就是数据包要转发给其它主机,这时候它会依次经过:
7. 进入 mangle 表的 FORWARD 链,这里也比较特殊,这是在第一次路由决定之后,在进行最后的路由决定之前,我们仍然可以对数据包进行某些修改。
8. 进入 filter 表的 FORWARD 链,在这里我们可以对所有转发的数据包进行过滤。需要注意的是:经过这里的数据包是转发的,方向是双向的。
9. 进入 mangle 表的 POSTROUTING 链,到这里已经做完了所有的路由决定,但数据包仍然在本地主机,我们还可以进行某些修改。
10. 进入 nat 表的 POSTROUTING 链,在这里一般都是用来做 SNAT ,不要在这里进行过滤。
11. 进入出去的网络接口。完毕。
另一种情况是,数据包就是发给本地主机的,那么它会依次穿过:
7. 进入 mangle 表的 INPUT 链,这里是在路由之后,交由本地主机之前,我们也可以进行一些相应的修改。
8. 进入 filter 表的 INPUT 链,在这里我们可以对流入的所有数据包进行过滤,无论它来自哪个网络接口。
9. 交给本地主机的应用程序进行处理。
10. 处理完毕后进行路由决定,看该往那里发出。
11. 进入 raw 表的 OUTPUT 链,这里是在连接跟踪处理本地的数据包之前。
12. 连接跟踪对本地的数据包进行处理。
13. 进入 mangle 表的 OUTPUT 链,在这里我们可以修改数据包,但不要做过滤。
14. 进入 nat 表的 OUTPUT 链,可以对防火墙自己发出的数据做 NAT 。
15. 再次进行路由决定。
16. 进入 filter 表的 OUTPUT 链,可以对本地出去的数据包进行过滤。
17. 进入 mangle 表的 POSTROUTING 链,同上一种情况的第9步。注意,这里不光对经过防火墙的数据包进行处理,还对防火墙自己产生的数据包进行处理。
18. 进入 nat 表的 POSTROUTING 链,同上一种情况的第10步。
19. 进入出去的网络接口。完毕。

三、iptables raw表的使用
增加raw表,在其他表处理之前,-j NOTRACK跳过其它表处理
状态除了以前的四个还增加了一个UNTRACKED
例如:
可以使用 “NOTRACK” target 允许规则指定80端口的包不进入链接跟踪/NAT子系统
iptables -t raw -A PREROUTING -d 1.2.3.4 -p tcp --dport 80 -j NOTRACK
iptables -t raw -A PREROUTING -s 1.2.3.4 -p tcp --sport 80 -j NOTRACK
iptables -A FORWARD -m state --state UNTRACKED -j ACCEPT
四、解决ip_conntrack: table full, dropping packet的问题

在启用了iptables web服务器上,流量高的时候经常会出现下面的错误:
ip_conntrack: table full, dropping packet
这个问题的原因是由于web服务器收到了大量的连接,在启用了iptables的情况下,iptables会把所有的连接都做链接跟踪处理,这样iptables就会有一个链接跟踪表,当这个表满的时候,就会出现上面的错误。
iptables的链接跟踪表最大容量为/proc/sys/net/ipv4/ip_conntrack_max,链接碰到各种状态的超时后就会从表中删除。
所以解決方法一般有两个:
(1) 加大 ip_conntrack_max 值
vi /etc/sysctl.conf
net.ipv4.ip_conntrack_max = 393216
net.ipv4.netfilter.ip_conntrack_max = 393216
(2): 降低 ip_conntrack timeout时间
vi /etc/sysctl.conf
net.ipv4.netfilter.ip_conntrack_tcp_timeout_established = 300
net.ipv4.netfilter.ip_conntrack_tcp_timeout_time_wait = 120
net.ipv4.netfilter.ip_conntrack_tcp_timeout_close_wait = 60
net.ipv4.netfilter.ip_conntrack_tcp_timeout_fin_wait = 120

上面两种方法打个比喻就是烧水水开的时候,换一个大锅。一般情况下都可以解决问题,但是在极端情况下,还是不够用,怎么办?
这样就得反其道而行,用釜底抽薪的办法。iptables的raw表是不做数据包的链接跟踪处理的,我们就把那些连接量非常大的链接加入到iptables raw表。
如一台web服务器可以这样:
iptables -t raw -A PREROUTING -d 1.2.3.4 -p tcp --dport 80 -j NOTRACK
iptables -A FORWARD -m state --state UNTRACKED -j ACCEPT
五、实例说明:
1、单个规则实例
iptables -F?
# -F 是清除的意思,作用就是把 FILTRE TABLE 的所有链的规则都清空
iptables -A INPUT -s 172.20.20.1/32 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
#在 FILTER 表的 INPUT 链匹配源地址是172.20.20.1的主机,状态分别是NEW,ESTABLISHED,RELATED 的都放行。
iptables -A INPUT -s 172.20.20.1/32 -m state --state NEW,ESTABLISHED -p tcp -m multiport --dport 123,110 -j ACCEPT
# -p 指定协议,-m 指定模块,multiport模块的作用就是可以连续匹配多各不相邻的端口号。完整的意思就是源地址是172.20.20.1的主机,状态分别是NEW, ESTABLISHED,RELATED的,TCP协议,目的端口分别为123 和 110 的数据包都可以通过。
iptables -A INPUT -s 172.20.22.0/24 -m state --state NEW,ESTABLISHED -p tcp -m multiport --dport 123,110 -j ACCEPT
iptables -A INPUT -s 0/0 -m state --state NEW -p tcp -m multiport --dport 123,110 -j DROP
#这句意思为源地址是0/0的 NEW状态的的TCP数据包都禁止访问我的123和110端口。
iptables -A INPUT -s ! 172.20.89.0/24 -m state --state NEW -p tcp -m multiport --dport 1230,110 -j DROP
# "!"号的意思 取反。就是除了172.20.89.0这个IP段的地址都DROP。
iptables -R INPUT 1 -s 192.168.6.99 -p tcp --dport 22 -j ACCEPT
替换INPUT链中的第一条规则
iptables -t filter -L INPUT -vn
以数字形式详细显示filter表INPUT链的规则
#-------------------------------NAT IP--------------------------------------
#以下操作是在 NAT TABLE 里面完成的。请大家注意。
iptables -t nat -F
iptables -t nat -A PREROUTING -d 192.168.102.55 -p tcp --dport 90 -j DNAT --to 172.20.11.1:800
#-A PREROUTING 指定在路由前做的。完整的意思是在 NAT TABLE 的路由前处理,目的地为192.168.102.55 的 目的端口为90的我们做DNAT处理,给他转向到172.20.11.1:800那里去。
iptables -t nat -A POSTROUTING -d 172.20.11.1 -j SNAT --to 192.168.102.55
#-A POSTROUTING 路由后。意思为在 NAT TABLE 的路由后处理,凡是目的地为 172.20.11.1 的,我们都给他做SNAT转换,把源地址改写成 192.168.102.55 。
iptables -A INPUT -d 192.168.20.0/255.255.255.0 -i eth1 -j DROP
iptables -A INPUT -s 192.168.20.0/255.255.255.0 -i eth1 -j DROP
iptables -A OUTPUT -d 192.168.20.0/255.255.255.0 -o eth1 -j DROP
iptables -A OUTPUT -s 192.168.20.0/255.255.255.0 -o eth1 -j DROP
# 上例中,eth1是一个与外部Internet相连,而192.168.20.0则是内部网的网络号,上述规则用来防止IP欺骗,因为出入eth1的包的ip应该是公共IP
iptables -A INPUT -s 255.255.255.255 -i eth0 -j DROP
iptables -A INPUT -s 224.0.0.0/224.0.0.0 -i eth0 -j DROP
iptables -A INPUT -d 0.0.0.0 -i eth0 -j DROP
# 防止广播包从IP代理服务器进入局域网:
iptables -A INPUT -p tcp -m tcp --sport 5000 -j DROP
iptables -A INPUT -p udp -m udp --sport 5000 -j DROP
iptables -A OUTPUT -p tcp -m tcp --dport 5000 -j DROP
iptables -A OUTPUT -p udp -m udp --dport 5000 -j DROP
# 屏蔽端口 5000
iptables -A INPUT -s 211.148.130.129 -i eth1 -p tcp -m tcp --dport 3306 -j DROP
iptables -A INPUT -s 192.168.20.0/255.255.255.0 -i eth0 -p tcp -m tcp --dport 3306 -j ACCEPT
iptables -A INPUT -s 211.148.130.128/255.255.255.240 -i eth1 -p tcp -m tcp --dport 3306 -j ACCEPT
iptables -A INPUT -p tcp -m tcp --dport 3306 -j DROP
# 防止 Internet 网的用户访问 MySQL 服务器(就是 3306 端口)
iptables -A FORWARD -p TCP --dport 22 -j REJECT --reject-with tcp-reset
#REJECT, 类似于DROP,但向发送该包的主机回复由--reject-with指定的信息,从而可以很好地隐藏防火墙的存在
2、www的iptables实例
#!/bin/bash
export PATH=/sbin:/usr/sbin:/bin:/usr/bin
#加载相关模块
modprobe iptable_nat
modprobe ip_nat_ftp
modprobe ip_nat_irc
modprobe ip_conntrack
modprobe ip_conntrack_ftp
modprobe ip_conntrack_irc
modprobe ipt_limit
echo 1 >;/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts
echo 0 >;/proc/sys/net/ipv4/conf/all/accept_source_route
echo 0 >;/proc/sys/net/ipv4/conf/all/accept_redirects
echo 1 >;/proc/sys/net/ipv4/icmp_ignore_bogus_error_responses
echo 1 >;/proc/sys/net/ipv4/conf/all/log_martians
echo 1 >;/proc/sys/net/ipv4/tcp_syncookies
iptables -F
iptables -X
iptables -Z
## 允许本地回路?Loopback - Allow unlimited traffic
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
## 防止SYN洪水?SYN-Flooding Protection
iptables -N syn-flood
iptables -A INPUT -i ppp0 -p tcp --syn -j syn-flood
iptables -A syn-flood -m limit --limit 1/s --limit-burst 4 -j RETURN
iptables -A syn-flood -j DROP
## 确保新连接是设置了SYN标记的包?Make sure that new TCP connections are SYN packets
iptables -A INPUT -i eth0 -p tcp ! --syn -m state --state NEW -j DROP
## 允许HTTP的规则
iptables -A INPUT -i ppp0 -p tcp -s 0/0 --sport 80 -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -i ppp0 -p tcp -s 0/0 --sport 443 -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -i ppp0 -p tcp -d 0/0 --dport 80 -j ACCEPT
iptables -A INPUT -i ppp0 -p tcp -d 0/0 --dport 443 -j ACCEPT
## 允许DNS的规则
iptables -A INPUT -i ppp0 -p udp -s 0/0 --sport 53 -m state --state ESTABLISHED -j ACCEPT
iptables -A INPUT -i ppp0 -p udp -d 0/0 --dport 53 -j ACCEPT
## IP包流量限制?IP packets limit
iptables -A INPUT -f -m limit --limit 100/s --limit-burst 100 -j ACCEPT
iptables -A INPUT -i eth0 -p icmp -j DROP
## 允许SSH
iptables -A INPUT -p tcp -s ip1/32 --dport 22 -j ACCEPT
iptables -A INPUT -p tcp -s ip2/32 --dport 22 -j ACCEPT
## 其它情况不允许?Anything else not allowed
iptables -A INPUT -i eth0 -j DROP
3、一个包过滤防火墙实例
环境:redhat9 加载了string time等模块
eth0 接外网──ppp0
eth1 接内网──192.168.0.0/24
#!/bin/sh
#
modprobe ipt_MASQUERADE
modprobe ip_conntrack_ftp
modprobe ip_nat_ftp
iptables -F
iptables -t nat -F
iptables -X
iptables -t nat -X
########################### INPUT键 ###################################
iptables -P INPUT DROP
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -p tcp -m multiport --dports 110,80,25 -j ACCEPT
iptables -A INPUT -p tcp -s 192.168.0.0/24 --dport 139 -j ACCEPT
#允许内网samba,smtp,pop3,连接
iptables -A INPUT -i eth1 -p udp -m multiport --dports 53 -j ACCEPT
#允许dns连接
iptables -A INPUT -p tcp --dport 1723 -j ACCEPT
iptables -A INPUT -p gre -j ACCEPT
#允许外网vpn连接
iptables -A INPUT -s 192.186.0.0/24 -p tcp -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -i ppp0 -p tcp --syn -m connlimit --connlimit-above 15 -j DROP
#为了防止DoS太多连接进来,那么可以允许最多15个初始连接,超过的丢弃
iptables -A INPUT -s 192.186.0.0/24 -p tcp --syn -m connlimit --connlimit-above 15 -j DROP
#为了防止DoS太多连接进来,那么可以允许最多15个初始连接,超过的丢弃
iptables -A INPUT -p icmp -m limit --limit 3/s -j LOG --log-level INFO --log-prefix "ICMP packet IN: "
iptables -A INPUT -p icmp -j DROP
#禁止icmp通信-ping 不通
iptables -t nat -A POSTROUTING -o ppp0 -s 192.168.0.0/24 -j MASQUERADE
#内网转发
iptables -N syn-flood
iptables -A INPUT -p tcp --syn -j syn-flood
iptables -I syn-flood -p tcp -m limit --limit 3/s --limit-burst 6 -j RETURN
iptables -A syn-flood -j REJECT
#防止SYN攻击 轻量
iptables -P FORWARD DROP
iptables -A FORWARD -p tcp -s 192.168.0.0/24 -m multiport --dports 80,110,21,25,1723 -j ACCEPT
iptables -A FORWARD -p udp -s 192.168.0.0/24 --dport 53 -j ACCEPT
iptables -A FORWARD -p gre -s 192.168.0.0/24 -j ACCEPT
iptables -A FORWARD -p icmp -s 192.168.0.0/24 -j ACCEPT
#允许 vpn客户走vpn网络连接外网
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -I FORWARD -p udp --dport 53 -m string --string "tencent" -m time --timestart 8:15 --timestop 12:30 --days Mon,Tue,Wed,Thu,Fri,Sat -j DROP
#星期一到星期六的8:00-12:30禁止qq通信
iptables -I FORWARD -p udp --dport 53 -m string --string "TENCENT" -m time --timestart 8:15 --timestop 12:30 --days Mon,Tue,Wed,Thu,Fri,Sat -j DROP
#星期一到星期六的8:00-12:30禁止qq通信
iptables -I FORWARD -p udp --dport 53 -m string --string "tencent" -m time --timestart 13:30 --timestop 20:30 --days Mon,Tue,Wed,Thu,Fri,Sat -j DROP
iptables -I FORWARD -p udp --dport 53 -m string --string "TENCENT" -m time --timestart 13:30 --timestop 20:30 --days Mon,Tue,Wed,Thu,Fri,Sat -j DROP
#星期一到星期六的13:30-20:30禁止QQ通信
iptables -I FORWARD -s 192.168.0.0/24 -m string --string "qq.com" -m time --timestart 8:15 --timestop 12:30 --days Mon,Tue,Wed,Thu,Fri,Sat -j DROP
#星期一到星期六的8:00-12:30禁止qq网页
iptables -I FORWARD -s 192.168.0.0/24 -m string --string "qq.com" -m time --timestart 13:00 --timestop 20:30 --days Mon,Tue,Wed,Thu,Fri,Sat -j DROP
#星期一到星期六的13:30-20:30禁止QQ网页
iptables -I FORWARD -s 192.168.0.0/24 -m string --string "ay2000 .NET " -j DROP
iptables -I FORWARD -d 192.168.0.0/24 -m string --string "宽频影院" -j DROP
iptables -I FORWARD -s 192.168.0.0/24 -m string --string "色情" -j DROP
iptables -I FORWARD -p tcp --sport 80 -m string --string "广告" -j DROP
#禁止ay2000 .Net ,宽频影院,色情,广告网页连接 !但中文 不是很理想
iptables -A FORWARD -m ipp2p --edk --kazaa --bit -j DROP
iptables -A FORWARD -p tcp -m ipp2p --ares -j DROP
iptables -A FORWARD -p udp -m ipp2p --kazaa -j DROP
#禁止BT连接
iptables -A FORWARD -p tcp --syn --dport 80 -m connlimit --connlimit-above 15 --connlimit-mask 24 -j DROP
#只允许每组ip同时15个80端口转发
#######################################################################
sysctl -w net.ipv4.ip_forward=1 &>/dev/null
#打开转发
#######################################################################
sysctl -w net.ipv4.tcp_syncookies=1 &>/dev/null
#打开 syncookie (轻量级预防 DOS 攻击)
sysctl -w net.ipv4.netfilter.ip_conntrack_tcp_timeout_established=3800 &>/dev/null
#设置默认 TCP 连接痴呆时长为 3800 秒(此选项可以大大降低连接数)
sysctl -w net.ipv4.ip_conntrack_max=300000 &>/dev/null
#设置支持最大连接树为 30W(这个根据你的内存和 iptables 版本来,每个 connection 需要 300 多个字节)
#######################################################################
iptables -I INPUT -s 192.168.0.50 -j ACCEPT
iptables -I FORWARD -s 192.168.0.50 -j ACCEPT
#192.168.0.50是我的机子,全部放行!

linux系统中查看己设置iptables规则
1、iptables -L

查看filter表的iptables规则,包括所有的链。 filter表包含INPUT、OUTPUT、FORWARD三个规则链 。
说明:-L是--list的简写,作用是列出规则。

2、iptables -L [-t 表名]

只查看某个表的中的规则。

说明: 表名一共有三个:filter,nat,mangle,如果没有指定表名,则默认查看filter表的规则 列表(就相当于第一条命令)。
举例:iptables -L -t filter

3、iptables -L [-t 表名] [链名]

这里多了个链名,就是规则链的名称。

说明: iptables一共有INPUT、OUTPUT、FORWARD、PREROUTING、POSTROUTING五个规则链 。

举例:iptables -L INPUT

注意: 链名必须大写 。在Linux系统上,命令的大小写很敏感。

4、iptables -n -L

说明:以数字形式显示规则。如果没有-n,规则中可能会出现anywhere,有了-n,它会变成0.0.0.0/0

5、iptables -nv -L

说明:你也可以使用“iptables -L -nv”来查看,这个列表看起来更详细,对技术人员更友好,呵呵。

如果想删除iptables规则我们可以如下操作

删除用-D参数

删除之前添加的规则(iptables -A INPUT -s 192.168.1.5 -j DROP):

[root @test ~]# iptables -D INPUT -s 192.168.1.5 -j DROP

有时候要删除的规则太长,删除时要写一大串,既浪费时间又容易写错,这时我们可以
先使用 –line-number找出该条规则的行号,再通过行号删除规则。

[root @test ~]# iptables -nv --line-number

iptables v1.4.7: no command specified
Try `iptables -h' or 'iptables --help' for more information.
[root @test ~]# iptables -nL --line-number
Chain INPUT (policy ACCEPT)
num target prot opt source destination
1 DROP all -- 192.168.1.1 0.0.0.0/0
2 DROP all -- 192.168.1.2 0.0.0.0/0
3 DROP all -- 192.168.1.3 0.0.0.0/0

删除第二行规则
[root @test ~]# iptables -D INPUT 2


[root @tp ~]# iptables -A INPUT -p tcp --dport 22 -j ACCEPT
[root@tp ~]# iptables -A OUTPUT -p tcp --sport 22 -j ACCEPT

(注:这个规则,如果你把OUTPUT 设置成DROP的就要写上这一部,好多人都是忘了写这一部规则导致,始终无法SSH.在远程一下,是不是好了.其他的端口也一样,如果开启了web服务器,OUTPUT设置成DROP的话,同样也要添加一条链:

[root@tp ~]# iptables -A OUTPUT -p tcp --sport 80 -j ACCEPT)

如果做了WEB服务器,开启80端口.

[root@tp ~]# iptables -A INPUT -p tcp --dport 80 -j ACCEPT

如果做了邮件服务器,开启25,110端口.
[root@tp ~]# iptables -A INPUT -p tcp --dport 110 -j ACCEPT
[root@tp ~]# iptables -A INPUT -p tcp --dport 25 -j ACCEPT

如果做了FTP服务器,开启21端口
[root@tp ~]# iptables -A INPUT -p tcp --dport 21 -j ACCEPT
[root@tp ~]# iptables -A INPUT -p tcp --dport 20 -j ACCEPT

如果做了DNS服务器,开启53端口
[root@tp ~]# iptables -A INPUT -p tcp --dport 53 -j ACCEPT

上面主要写的都是INPUT链,凡是不在上面的规则里的,都DROP

允许icmp包通过,也就是允许ping,

[root@tp ~]# iptables -A OUTPUT -p icmp -j ACCEPT (OUTPUT设置成DROP的话)

[root@tp ~]# iptables -A INPUT -p icmp -j ACCEPT (INPUT设置成DROP的话)

允许loopback!(不然会导致DNS无法正常关闭等问题)

IPTABLES -A INPUT -i lo -p all -j ACCEPT (如果是INPUT DROP)

IPTABLES -A OUTPUT -o lo -p all -j ACCEPT(如果是OUTPUT DROP)

下面写OUTPUT链,OUTPUT链默认规则是ACCEPT,所以我们就写需要DROP(放弃)的链.

减少不安全的端口连接

[root@tp ~]# iptables -A OUTPUT -p tcp --sport 31337 -j DROP
[root@tp ~]# iptables -A OUTPUT -p tcp --dport 31337 -j DROP

.

下面写一下更加细致的规则,就是限制到某台机器

如:我们只允许192.168.0.3的机器进行SSH连接

[root@tp ~]# iptables -A INPUT -s 192.168.0.3 -p tcp --dport 22 -j ACCEPT

如果要允许,或 限制一段IP地址可用 192.168.0.0/24 表示192.168.0.1-255端的所有IP .

24表示子网掩码数 .但要记得把 /etc/sysconfig/iptables 里的这一行删了.

-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT 因为它表示所有地址都可以登陆.

或采用命令方式:

[root@tp ~]# iptables -D INPUT -p tcp --dport 22 -j ACCEPT


[root@tp ~]# /etc/rc.d/init.d/iptables save

这样写 !192.168.0.3 表示除了192.168.0.3的ip地址

其他的规则连接也一样这么设置.

在下面就是FORWARD链, FORWARD链的默认规则是DROP,所以我们就写需要ACCETP(通过)的链 ,对正在转发链的监控.

开启转发功能,(在做NAT时,FORWARD默认规则是DROP时,必须做)

[root@tp ~]# iptables -A FORWARD -i eth0 -o eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT
[root@tp ~]# iptables -A FORWARD -i eth1 -o eh0 -j ACCEPT

丢弃坏的TCP包
[root@tp ~]# iptables -A FORWARD -p TCP ! --syn -m state --state NEW -j DROP
处理IP碎片数量,防止攻击,允许每秒100个

[root@tp ~]# iptables -A FORWARD -f -m limit --limit 100/s --limit-burst 100 -j ACCEPT

设置ICMP包过滤,允许每秒1个包,限制触发条件是10个包.
[root@tp ~]# iptables -A FORWARD -p icmp -m limit --limit 1/s --limit-burst 10 -j ACCEPT
我在前面只所以允许ICMP包通过,就是因为我在这里有限制.

3、删除
删除用-D参数
删除之前添加的规则(iptables -A INPUT -s 192.168.1.5 -j DROP):
[root@test ~]# iptables -D INPUT -s 192.168.1.5 -j DROP有时候要删除的规则太长,删除时要写一大串,既浪费时间又容易写错,这时我们可以先使用–line-number找出该条规则的行号,再通过行号删除规则。
[root@test ~]# iptables -nv --line-number
iptables v1.4.7: no command specified
Try `iptables -h' or 'iptables --help' for more information.
[root@test ~]# iptables -nL --line-number
Chain INPUT (policy ACCEPT)
num target prot opt source destination
1 DROP all -- 192.168.1.1 0.0.0.0/0
2 DROP all -- 192.168.1.2 0.0.0.0/0
3 DROP all -- 192.168.1.3 0.0.0.0/0
删除第二行规则
[root@test ~]# iptables -D INPUT 24、

不管你在安装linux时是否启动了防火墙,如果你想配置属于自己的防火墙,那就清除现在filter的所有规则.
[root@tp ~]# iptables -F 清除预设表filter中的所有规则链的规则

[root@tp ~]# iptables -X 清除预设表filter中使用者自定链中的规则

我们在来看一下
[root@tp ~]# iptables -L -n
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain FORWARD (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination

[root@tp ~]# /etc/rc.d/init.d/iptables save
这样就可以写到/etc/sysconfig/iptables文件里了.写入后记得把防火墙重起一下,才能起作用.
[root@tp ~]# service iptables restart
现在IPTABLES配置表里什么配置都没有了,那我们开始我们的配置吧

(3)设定预设规则

[root@tp ~]# iptables -p INPUT DROP

[root@tp ~]# iptables -p OUTPUT ACCEPT

[root@tp ~]# iptables -p FORWARD DROP

上面的意思是,当超出了IPTABLES里filter表里的两个链规则(INPUT,FORWARD)时,不在这两个规则里的数据包怎么处理呢,那就是DROP(放弃).应该说这样配置是很安全的.我们要控制流入数据包

而对于OUTPUT链,也就是流出的包我们不用做太多限制,而是采取ACCEPT,也就是说,不在着个规则里的包怎么办呢,那就是通过.

可以看出INPUT,FORWARD两个链采用的是允许什么包通过,而OUTPUT链采用的是不允许什么包通过.

修改使用-R参数

先看下当前规则:
[root@test ~]# iptables -nL --line-number
Chain INPUT (policy ACCEPT)
num target prot opt source destination
1 DROP all -- 192.168.1.1 0.0.0.0/0
2 DROP all -- 192.168.1.2 0.0.0.0/0
3 DROP all -- 192.168.1.5 0.0.0.0/0
将第三条规则改为ACCEPT:
[root@test ~]# iptables -R INPUT 3 -j ACCEPT 再查看下:
[root@test ~]# iptables -nL --line-number
Chain INPUT (policy ACCEPT)
num target prot opt source destination
1 DROP all -- 192.168.1.1 0.0.0.0/0
2 DROP all -- 192.168.1.2 0.0.0.0/0
3 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
第三条规则的target已改为ACCEPT。

iptables -E old-chain-name new-chain-name

-E 旧的链名 新的链名

用新的链名取代旧的链名

说明

Iptalbes 是用来设置、维护和检查Linux内核的IP包过滤规则的。

可以定义不同的表,每个表都包含几个内部的链,也能包含用户定义的链。每个链都是一个规则列表,对对应的包进行匹配:每条规则指定应当如何处理与之相匹配的包。这被称作'target'(目标),也可以跳向同一个表内的用户定义的链。

TARGETS

防火墙的规则指定所检查包的特征,和目标。如果包不匹配,将送往该链中下一条规则检查;如果匹配,那么下一条规则由目标值确定.该目标值可以是用户定义的链名,或是某个专用值,如ACCEPT[通过], DROP[删除], QUEUE[排队], 或者 RETURN[返回]。
ACCEPT 表示让这个包通过。DROP表示将这个包丢弃。QUEUE表示把这个包传递到用户空间。RETURN表示停止这条链的匹配,到前一个链的规则重新开始。如果到达了一个内建的链(的末端),或者遇到内建链的规则是RETURN,包的命运将由链准则指定的目标决定。
TABLES

当前有三个表(哪个表是当前表取决于内核配置选项和当前模块)。

-t table
这个选项指定命令要操作的匹配包的表。如果内核被配置为自动加载模块,这时若模块没有加载,(系统)将尝试(为该表)加载适合的模块。这些表如下:

filter,这是默认的表 ,包含了内建的链INPUT(处理进入的包)、FORWORD(处理通过的包)和OUTPUT(处理本地生成的包)。

nat,这个表被查询时表示遇到了产生新的连接的包,由三个内建的链构成:PREROUTING (修改到来的包)、OUTPUT(修改路由之前本地的包)、POSTROUTING(修改准备出去的包)。

mangle 这个表用来对指定的包进行修改。它有两个内建规则:PREROUTING(修改路由之前进入的包)和OUTPUT(修改路由之前本地的包)。

OPTIONS

这些可被iptables识别的选项可以区分不同的种类。

COMMANDS

这些选项指定执行明确的动作:若指令行下没有其他规定,该行只能指定一个选项.对于长格式的命令和选项名,所用字母长度只要保证iptables能从其他选项中区分出该指令就行了。
-A -append
在所选择的链末添加一条或更多规则。当源(地址)或者/与 目的(地址)转换为多个地址时,这条规则会加到所有可能的地址(组合)后面。
-D -delete
从所选链中删除一条或更多规则。这条命令可以有两种方法:可以把被删除规则指定为链中的序号(第一条序号为1),或者指定为要匹配的规则。
-R -replace
从选中的链中取代一条规则。如果源(地址)或者/与 目的(地址)被转换为多地址,该命令会失败。规则序号从1开始。
-I -insert
根据给出的规则序号向所选链中插入一条或更多规则。所以,如果规则序号为1,规则会被插入链的头部。这也是不指定规则序号时的默认方式。
-L -list
显示所选链的所有规则。如果没有选择链,所有链将被显示。也可以和z选项一起使用,这时链会被自动列出和归零。精确输出受其它所给参数影响。
-F -flush
清空所选链。这等于把所有规则一个个的删除。
--Z -zero
把所有链的包及字节的计数器清空。它可以和 -L配合使用,在清空前察看计数器,请参见前文。
-N -new-chain
根据给出的名称建立一个新的用户定义链。这必须保证没有同名的链存在。
-X -delete-chain
删除指定的用户自定义链。这个链必须没有被引用,如果被引用,在删除之前你必须删除或者替换与之有关的规则。如果没有给出参数,这条命令将试着删除每个非内建的链。

-P -policy
设置链的目标规则。
-E -rename-chain
根据用户给出的名字对指定链进行重命名,这仅仅是修饰,对整个表的结构没有影响。TARGETS参数给出一个合法的目标。只有非用户自定义链可以使用规则,而且内建链和用户自定义链都不能是规则的目标。
-h Help.
帮助。给出当前命令语法非常简短的说明。
PARAMETERS
参数
以下参数构成规则详述,如用于add、delete、replace、append 和 check命令。
-p -protocal [!]protocol
规则或者包检查(待检查包)的协议。指定协议可以是tcp、udp、icmp中的一个或者全部,也可以是数值,代表这些协议中的某一个。当然也可以使用在/etc/protocols中定义的协议名。在协议名前加上"!"表示相反的规则。数字0相当于所有all。Protocol all会匹配所有协议,而且这是缺省时的选项。在和check命令结合时,all可以不被使用。
-s -source [!] address[/mask]
指定源地址,可以是主机名、网络名和清楚的IP地址。mask说明可以是网络掩码或清楚的数字,在网络掩码的左边指定网络掩码左边"1"的个数,因此,mask值为24等于255.255.255.0。在指定地址前加上"!"说明指定了相反的地址段。标志 --src 是这个选项的简写。
-d --destination [!] address[/mask]
指定目标地址,要获取详细说明请参见 -s标志的说明。标志 --dst 是这个选项的简写。
-j --jump target
-j 目标跳转
指定规则的目标;也就是说,如果包匹配应当做什么。目标可以是用户自定义链(不是这条规则所在的),某个会立即决定包的命运的专用内建目标,或者一个扩展(参见下面的EXTENSIONS)。如果规则的这个选项被忽略,那么匹配的过程不会对包产生影响,不过规则的计数器会增加。
-i -in-interface [!] [name]
i -进入的(网络)接口 [!][名称]
这是包经由该接口接收的可选的入口名称,包通过该接口接收(在链INPUT、FORWORD和PREROUTING中进入的包)。当在接口名前使用"!"说明后,指的是相反的名称。如果接口名后面加上"+",则所有以此接口名开头的接口都会被匹配。如果这个选项被忽略,会假设为"+",那么将匹配任意接口。
-o --out-interface [!][name]
-o --输出接口[名称]
这是包经由该接口送出的可选的出口名称,包通过该口输出(在链FORWARD、OUTPUT和POSTROUTING中送出的包)。当在接口名前使用"!"说明后,指的是相反的名称。如果接口名后面加上"+",则所有以此接口名开头的接口都会被匹配。如果这个选项被忽略,会假设为"+",那么将匹配所有任意接口。
[!] -f, --fragment
[!] -f --分片
这意味着在分片的包中,规则只询问第二及以后的片。自那以后由于无法判断这种把包的源端口或目标端口(或者是ICMP类型的),这类包将不能匹配任何指定对他们进行匹配的规则。如果"!"说明用在了"-f"标志之前,表示相反的意思。
OTHER OPTIONS
其他选项
还可以指定下列附加选项:
-v --verbose
-v --详细
详细输出。这个选项让list命令显示接口地址、规则选项(如果有)和TOS(Type of Service)掩码。包和字节计数器也将被显示,分别用K、M、G(前缀)表示1000、1,000,000和1,000,000,000倍(不过请参看-x标志改变它),对于添加,插入,删除和替换命令,这会使一个或多个规则的相关详细信息被打印。
-n --numeric
-n --数字
数字输出。IP地址和端口会以数字的形式打印。默认情况下,程序试显示主机名、网络名或者服务(只要可用)。
-x -exact
-x -精确
扩展数字。显示包和字节计数器的精确值,代替用K,M,G表示的约数。这个选项仅能用于 -L 命令。
--line-numbers
当列表显示规则时,在每个规则的前面加上行号,与该规则在链中的位置相对应。
MATCH EXTENSIONS
对应的扩展
iptables能够使用一些与模块匹配的扩展包。以下就是含于基本包内的扩展包,而且他们大多数都可以通过在前面加上!来表示相反的意思。
tcp
当 --protocol tcp 被指定,且其他匹配的扩展未被指定时,这些扩展被装载。它提供以下选项:
--source-port [!] [port[:port]]
源端口或端口范围指定。这可以是服务名或端口号。使用格式端口:端口也可以指定包含的(端口)范围。如果首端口号被忽略,默认是"0",如果末端口号被忽略,默认是"65535",如果第二个端口号大于第一个,那么它们会被交换。这个选项可以使用 --sport的别名。
--destionation-port [!] [port:[port]]
目标端口或端口范围指定。这个选项可以使用 --dport别名来代替。
--tcp-flags [!] mask comp
匹配指定的TCP标记。第一个参数是我们要检查的标记,一个用逗号分开的列表,第二个参数是用逗号分开的标记表,是必须被设置的。标记如下:SYN ACK FIN RST URG PSH ALL NONE。因此这条命令:iptables -A FORWARD -p tcp --tcp-flags SYN, ACK, FIN, RST SYN只匹配那些SYN标记被设置而ACK、FIN和RST标记没有设置的包。
[!] --syn
只匹配那些设置了SYN位而清除了ACK和FIN位的TCP包。这些包用于TCP连接初始化时发出请求;例如,大量的这种包进入一个接口发生堵塞时会阻止进入的TCP连接,而出去的TCP连接不会受到影响。这等于 --tcp-flags SYN, RST, ACK SYN。如果"--syn"前面有"!"标记,表示相反的意思。
--tcp-option [!] number
匹配设置了TCP选项的。
udp
当protocol udp 被指定,且其他匹配的扩展未被指定时,这些扩展被装载,它提供以下选项:
--source-port [!] [port:[port]]
源端口或端口范围指定。详见 TCP扩展的--source-port选项说明。
--destination-port [!] [port:[port]]
目标端口或端口范围指定。详见 TCP扩展的--destination-port选项说明。
icmp
当protocol icmp被指定,且其他匹配的扩展未被指定时,该扩展被装载。它提供以下选项:
--icmp-type [!] typename
这个选项允许指定ICMP类型,可以是一个数值型的ICMP类型,或者是某个由命令iptables -p icmp -h所显示的ICMP类型名。
mac
--mac-source [!] address
匹配物理地址。必须是XX:XX:XX:XX:XX这样的格式。注意它只对来自以太设备并进入PREROUTING、FORWORD和INPUT链的包有效。
limit
这个模块匹配标志用一个标记桶过滤器一一定速度进行匹配,它和LOG目标结合使用来给出有限的登陆数.当达到这个极限值时,使用这个扩展包的规则将进行匹配.(除非使用了"!"标记)
--limit rate
最大平均匹配速率:可赋的值有'/second', '/minute', '/hour', or '/day'这样的单位,默认是3/hour。
--limit-burst number
待匹配包初始个数的最大值:若前面指定的极限还没达到这个数值,则概数字加1.默认值为5
multiport
这个模块匹配一组源端口或目标端口,最多可以指定15个端口。只能和-p tcp 或者 -p udp 连着使用。
--source-port [port[, port]]
如果源端口是其中一个给定端口则匹配
--destination-port [port[, port]]
如果目标端口是其中一个给定端口则匹配
--port [port[, port]]
若源端口和目的端口相等并与某个给定端口相等,则匹配。
mark
这个模块和与netfilter过滤器标记字段匹配(就可以在下面设置为使用MARK标记)。
--mark value [/mask]
匹配那些无符号标记值的包(如果指定mask,在比较之前会给掩码加上逻辑的标记)。
owner
此模块试为本地生成包匹配包创建者的不同特征。只能用于OUTPUT链,而且即使这样一些包(如ICMP ping应答)还可能没有所有者,因此永远不会匹配。
--uid-owner userid
如果给出有效的user id,那么匹配它的进程产生的包。
--gid-owner groupid
如果给出有效的group id,那么匹配它的进程产生的包。
--sid-owner seessionid
根据给出的会话组匹配该进程产生的包。
state
此模块,当与连接跟踪结合使用时,允许访问包的连接跟踪状态。
--state state
这里state是一个逗号分割的匹配连接状态列表。可能的状态是:INVALID表示包是未知连接,ESTABLISHED表示是双向传送的连接,NEW表示包为新的连接,否则是非双向传送的,而RELATED表示包由新连接开始,但是和一个已存在的连接在一起,如FTP数据传送,或者一个ICMP错误。
unclean
此模块没有可选项,不过它试着匹配那些奇怪的、不常见的包。处在实验中。
tos
此模块匹配IP包首部的8位tos(服务类型)字段(也就是说,包含在优先位中)。
--tos tos
这个参数可以是一个标准名称,(用iptables -m tos -h 察看该列表),或者数值。
TARGET EXTENSIONS
iptables可以使用扩展目标模块:以下都包含在标准版中。
LOG
为匹配的包开启内核记录。当在规则中设置了这一选项后,linux内核会通过printk()打印一些关于全部匹配包的信息(诸如IP包头字段等)。
--log-level level
记录级别(数字或参看 syslog.conf(5))。
--log-prefix prefix
在纪录信息前加上特定的前缀:最多14个字母长,用来和记录中其他信息区别。
--log-tcp-sequence
记录TCP序列号。如果记录能被用户读取那么这将存在安全隐患。
--log-tcp-options
记录来自TCP包头部的选项。
--log-ip-options
记录来自IP包头部的选项。
MARK
用来设置包的netfilter标记值。只适用于mangle表。
--set-mark mark
REJECT
作为对匹配的包的响应,返回一个错误的包:其他情况下和DROP相同。
此目标只适用于INPUT、FORWARD和OUTPUT链,和调用这些链的用户自定义链。这几个选项控制返回的错误包的特性:
--reject-with type
Type可以是icmp-net-unreachable、icmp-host-unreachable、icmp-port-nreachable、icmp-proto-unreachable、 icmp-net-prohibited 或者 icmp-host-prohibited,该类型会返回相应的ICMP错误信息(默认是port-unreachable)。选项 echo-reply也是允许的;它只能用于指定ICMP ping包的规则中,生成ping的回应。最后,选项tcp-reset可以用于在INPUT链中,或自INPUT链调用的规则,只匹配TCP协议:将回应一个TCP RST包。
TOS
用来设置IP包的首部八位tos。只能用于mangle表。
--set-tos tos
你可以使用一个数值型的TOS 值,或者用iptables -j TOS -h 来查看有效TOS名列表。
MIRROR
这是一个试验示范目标,可用于转换IP首部字段中的源地址和目标地址,再传送该包,并只适用于INPUT、FORWARD和OUTPUT链,以及只调用它们的用户自定义链。
SNAT
这个目标只适用于nat表的POSTROUTING链。它规定修改包的源地址(此连接以后所有的包都会被影响),停止对规则的检查,它包含选项:
--to-source [-][:port-port]
可以指定一个单一的新的IP地址,一个IP地址范围,也可以附加一个端口范围(只能在指定-p tcp 或者-p udp的规则里)。如果未指定端口范围,源端口中512以下的(端口)会被安置为其他的512以下的端口;512到1024之间的端口会被安置为1024以下的,其他端口会被安置为1024或以上。如果可能,端口不会被修改。
--to-destiontion [-][:port-port]
可以指定一个单一的新的IP地址,一个IP地址范围,也可以附加一个端口范围(只能在指定-p tcp 或者-p udp的规则里)。如果未指定端口范围,目标端口不会被修改。
MASQUERADE
只用于nat表的POSTROUTING链。只能用于动态获取IP(拨号)连接:如果你拥有静态IP地址,你要用SNAT。伪装相当于给包发出时所经过接口的IP地址设置一个映像,当接口关闭连接会终止。这是因为当下一次拨号时未必是相同的接口地址(以后所有建立的连接都将关闭)。它有一个选项:
--to-ports [-port>]
指定使用的源端口范围,覆盖默认的SNAT源地址选择(见上面)。这个选项只适用于指定了-p tcp或者-p udp的规则。
REDIRECT
只适用于nat表的PREROUTING和OUTPUT链,和只调用它们的用户自定义链。它修改包的目标IP地址来发送包到机器自身(本地生成的包被安置为地址127.0.0.1)。它包含一个选项:
--to-ports [ ]
指定使用的目的端口或端口范围:不指定的话,目标端口不会被修改。只能用于指定了-p tcp 或 -p udp的规则。
DIAGNOSTICS
诊断
不同的错误信息会打印成标准错误:退出代码0表示正确。类似于不对的或者滥用的命令行参数错误会返回错误代码2,其他错误返回代码为1。
BUGS
臭虫
Check is not implemented (yet).
检查还未完成。
COMPATIBILITY WITH IPCHAINS
与ipchains的兼容性
iptables和Rusty Russell的ipchains非常相似。主要区别是INPUT 链只用于进入本地主机的包,而OUTPUT只用于自本地主机生成的包。因此每个包只经过三个链的一个;以前转发的包会经过所有三个链。其他主要区别是 -i 引用进入接口;-o引用输出接口,两者都适用于进入FORWARD链的包。当和可选扩展模块一起使用默认过滤器表时,iptables是一个纯粹的包过滤器。这能大大减少以前对IP伪装和包过滤结合使用的混淆,所以以下选项作了不同的处理:
-j MASQ
-M -S
-M -L
在iptables中有几个不同的链。
linux iptables 端口 查看,打开,关闭
iptables 最常用的命令整理
查看哪些端口被打开
netstat -anp
关闭端口号
iptables -A INPUT -p tcp --drop 端口号 -j DROP
iptables -A OUTPUT -p tcp --dport 端口号 -j DROP
打开端口号
iptables -A INPUT -ptcp --dport 端口号 -j ACCEPT
将某些规则的包 log 到 /var/log/message 中
iptables -I {position} INPUT -J log --prefix "xxx"
iptables的日志管理
原贴地址:http://blog.chinaunix.net/u/31547/showart_366066.html
iptables的日志(log)由syslogd纪录和管理。初始存放在 /var/log/messages里面。自动采取循环纪录(rotation)的方式记录。但是由于混在 messages中,对于管理和监视产生了不便。这里,我简单介绍一下我的 iptables日志的管理,循环,和自动报告生成的经验:
由于iptables是linux的内核本身的功能,由dmesg或syslogd的facility结合内核管理。iptables的日志的初始值是[warn(=4)], 需要修改 syslog.conf。
---------------------------------------------------------------
:
kern.=warn /var/log/kern-warn-log←可以自己决定文件名
---------------------------------------------------------------
这里,facility在[kern]是 priority 的[warn], 日志将被记录在 /var/log/kern-warn-log 。
日志循环的设置方法:
在 /etc/logrotated.d/syslog 中追加以下语句:
--------------------------------------------------------------
:
/var/log/kern-warn-log {
rotate 50 剩余文件数
postrotate
/bin/kill -HUP `cat /var/run/syslogd.pid 2>/dev/null` 2>/dev/null || true
endscript
}
--------------------------------------------------------------
/etc/logrotate.conf的初始设置是每周一进行一次log的循环。所以每周的日志将被存在 /var/log/kern-warn-log 中,之前的旧日志将被顺次存储在 kern-warn-log.1 ----- kern-warn-log.50 中。
另外还有一种方法就是 通过iptables直接获取日志:(一般不用)
# iptables -A INPUT -s 127.0.0.1 -p icmp -j LOG --log-prefix "iptables icmp-localhost "←保存从eth0 进入的packet纪录
# iptables -A INPUT -s 127.0.0.1 -p icmp -j DROP←废除从eth0进入的packet纪录
这样一来,/var/log/kern-warn-log的内容将如下所示:
Sep 23 10:16:14 hostname kernel: iptables icmp-localhost IN=lo OUT= MAC=00:00:00:00:00:00:00:00:00:00:00:00:08:00 SRC=127.0.0.1 DST=127.0.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=ICMP TYPE=8 CODE=0 ID=57148 SEQ=256
iptables 查看客户端流量情况
作为路由时的流量统计:
如果服务器是做路由的,那么使用上面的命令把规则插入至INPUT/OUTPUT链中就无效了,因为那些IP根本不是在你的服务器上。但是那些IP的流量,都需要经过你的服务器的转发,因此把对于的规则插入至FORWARD表即可。、 例如我要统计IP为192.168.0.10的服务器
入网流量:
iptables -I FORWARD -d 192.168.0.10
统计该IP的出网流量:
iptables -I FORWARD -s 192.168.0.10
需要注意的是,此时添加统计出网和入网流量的规则,不再需要分INPUT链和OUTPUT链,因为你服务器只负责根据目标IP,进行转发。看图后你应该能明白:
查看命令:iptables -L -nv | grep 192
利用 iptables 命令监控端口流量
添加需要统计的端口
输入监控
监控 443 端口的输入流量 –dport
iptables -A INPUT -p tcp --dport 443
输出监控
监控 443 端口的输出流量 –sport
iptables -A OUTPUT -p tcp --sport 443
查看统计数据
iptables -L -v -n
重置统计数据
重置所有输入端口
iptables -Z INPUT
重置所有输出端口
iptables -Z OUTPUT
移除统计端口
移除输入端口
iptables -D INPUT -p tcp --dport 443
移除输出端口
iptables -D OUTPUT -p tcp --sport 443
使用iptables配置linux服务器请求转发

防火墙转发规则:
1首先把/etc/sysctl.conf配置文件中的net.ipv4.ip_forward=0改为net.ipv4.ip_forward=1
然后执行sysctl -p /etc/sysctl.conf使命令生效。

2配置防火墙的 IP 伪装

iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

3如果你想让内部网络内的某个服务器能够被外部访问,你可以使用 NAT 内 PREROUTING 链的 -j DNAT 目标来指定向目标 IP 地址以及端口转发请求连接到内部服务器。例如,如果你想把进入的 HTTP 请求转发到 172.11.0.21 上的专用 Apache HTTP 服务器服务器系统,运行以下命令
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j DNAT \ --to 172.11.0.21:80

4该规则允许把进入的80端口请求从防火墙转发到172.11.0.21的服务器。
iptables -A FORWARD -i eth0 -p tcp --dport 80 -d 172.11.0.21 -j ACCEPT
iptables规则的查看和清除
一 iptables查看基本语法
iptables [-t tables] [-L] [-nv]
选项与参数:
-t:后面接table,例如nat或filter,若省略,则使用默认的filter
-L:列出目前的table的规则
-n:不进行IP与HOSTNAME的反查,显示信息速度回快很多。
-v:列出更多的信息,包括通过该规则的数据包总位数、相关的网络接口等

二 iptables查看应用
列出filter table 3条链的规则
target:代表进行的操作,ACCEPT是放行,而REJECT则是拒绝,此外,还有DROP表示丢弃。
prot:代表使用的数据包协议,主要有TCP、UDP以及ICMP3种数据包格式。
opt:额外选项说明。
source:代表此规则是针对哪个来源IP进行限制。
destination:代表此规则是针对哪个目标进行限制。

三 iptable-save语法
iptables-save [-t table]
选项与参数:
-t:可以针对某些数据表格来输出,例如针对NAT或Filter等。
该命令会完整列出防火墙的规则

四 iptable-save应用
列出filter table的规则

五 清除iptables的语法
iptables [-t tables] [-FXZ]
选项与参数:
-F:清除所有制订的规则
-X:清除所有用户“自定义”的chain
-Z:将所有chain的计数与流量统计都归零

六 清除iptables的应用



参考链接:https://blog.csdn.net/aaa978464597/article/details/80244644#commentBox


扩展
1. iptables应用在一个网段 http://www.aminglinux.com/bbs/thread-177-1-1.html
2. sant,dnat,masquerade http://www.aminglinux.com/bbs/thread-7255-1-1.html
3. iptables限制syn速率 http://www.aminglinux.com/bbs/thread-985-1-1.html
云计算
2018-04-13 18:03:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
介绍
Horizon允许在不修改默认设置的情况下添加仪表板、面板和面板组。 插件化的设置是一种机制,允许将设置存储在单独的文件中。 这些文件在启动时被读取,并用于修改默认设置。
仪表板配置文件的默认位置是 openstack_dashboard/enabled , 使用另一个目录 openstack_dashboard/local/enabled 是用于本地覆盖。 这两组文件都将被加载, 但是 openstack_dashboard/local/enabled 设置将覆盖默认设置。 这些设置应用文件名的字母顺序。 如果相同的仪表板在 enabled 和 local/enabled 都有配置文件,在 local/enabled 将会被使用。 注意,因为python模块的名称不能以数字开头, 这些文件通常以一个主要的下划线和一个数字来命名,这样您就可以轻松地控制它们的顺序。
一般的可插件化设置 ¶
在描述具体的用例之前,可以在任何插件化的设置文件中使用以下配置变量.
ADD_EXCEPTIONS ¶ New in version 2014.1(Icehouse).
可以向配置变量 HORIZON['exceptions'] 里加 异常类字典。
ADD_INSTALLED_APPS New in version 2014.1(Icehouse).
应用程序列表插入到 INSTALLED_APPS 列表之前,当需要从插件中暴露静态文件时需要使用此设置。
ADD_ANGULAR_MODULES New in version 2014.2(Juno).
当Angular bootstraps时, AngularJS模块列表会被加载。 这些模块会作为依赖被添加到root Horizon application horizon 。
ADD_JS_FILES New in version 2014.2(Juno).
javascript源文件列表会被引进每个页面上加载的压缩文件集中。 这对于AngularJS模块来说是必需的,因为这些被添加进 ADD_ANGULAR_MODULES 的模块会引用到 javascript源文件, 因此需要在每个页面中加进来。
ADD_JS_SPEC_FILES ¶ New in version 2015.1(Kilo).
javascript规范文件的列表会被引入来与 Jasmine规范运行器集成。Jasmine是一个用于测试JavaScript代码的行为驱动开发框架。
ADD_SCSS_FILES ¶ New in version 8.0.0(Liberty).
scss文件列表会被引进每个页面上加载的压缩文件集中。 建议每个仪表板上一个scss文件,如果需要为面板引用额外的scss文件,请使用@import。
ADD_XSTATIC_MODULES ¶ New in version 14.0.0(Rocky).
包含javascript和scss文件的xstatic模块列表会被引进每个页面上加载的压缩文件集中。相关的文件应该在 ADD_XSTATIC_MODULES 进行添加而不是在 ADD_JS_FILES 添加。 这个选项配置值为一个元组列表,每个元组包含一个xstatic模块和一个要加载的javascript文件列表。 有关更多细节,请查看 openstack_dashboard/utils/settings.py 中的 BASE_XSTATIC_MODULES 注释。
例子: ADD_XSTATIC_MODULES = [ ('xstatic.pkg.foo', ['foo.js']), ('xstatic.pkg.bar', None), ]
AUTO_DISCOVER_STATIC_FILES ¶ New in version 8.0.0(Liberty).
如果设置为True, JavaScript文件和angular静态html模板文件将自动地从ADD_INSTALLED_APPS中的每个 app的静态文件夹中发现。
JavaScript源文件将根据命名约定进行排序: 首先列出带有 .module.js 扩展名的文件, 接着是其他的JavaScript源文件。
用于测试的JavaScript文件也将根据命名约定进行排序:首先列出带有 .mock.js 扩展名的文件,接着是 带有.spec.js扩展名的文件。
如果 ADD_JS_FILES 与/或者 ADD_JS_SPEC_FILES 也被指定,手动列出的文件将被附加到自动发现的文件。
DISABLED ¶ New in version 2014.1(Icehouse).
如果设置为True, 此设置文件不会添加到设置中。
EXTRA_TABS ¶ New in version 14.0.0(Rocky).
额外的选项卡可以添加到一个选项卡组中,通过使用这个设置来实现在horizon或其他的horizon插件。 在对应的选项卡组中定义的默认选项卡后将显示额外的选项卡。
这是一个字典配置。字典的key 指定添加额外标签的标签组。key必须匹配目标选项卡组的完整类名。 字典值是一个额外标签类的全称列表(此值的模块名和类名必须用句点分隔)。 通过 EXTRA_TABS 指定的tabs将按照注册的顺序显示。
有些情况下,您可能希望指定额外选项卡的顺序,因为多个horizon插件可以注册额外的选项卡。 可以在 EXTRA_TABS 配置中,通过使用带有优先权和选项卡类名的元组而不是一个额外选项卡全名作为字典值的方式来指定每个选项卡的优先权。优先级是一个整数,数值小的优先级高。如果有个选项卡忘记设置优先权,它的优先权会被设为0。
例子: EXTRA_TABS = { 'openstack_dashboard.dashboards.project.networks.tabs.NetworkDetailsTabs': ( 'openstack_dashboard.dashboards.project.networks.subnets.tabs.SubnetsTab', 'openstack_dashboard.dashboards.project.networks.ports.tabs.PortsTab', ), }
带优先权的例子: EXTRA_TABS = { 'openstack_dashboard.dashboards.project.networks.tabs.NetworkDetailsTabs': ( (1, 'openstack_dashboard.dashboards.project.networks.subnets.tabs.SubnetsTab'), (2, 'openstack_dashboard.dashboards.project.networks.ports.tabs.PortsTab'), ), }
UPDATE_HORIZON_CONFIG ¶ New in version 2014.2(Juno).
设置的字典值会更新 HORIZON_CONFIG 中数值。
Dashboards的插件化设置 ¶ New in version 2014.1(Icehouse).
下面的配置是用于注册仪表板的。
DASHBOARD ¶ New in version 2014.1(Icehouse).
此值为dashboard的slug字段值,会被添加到 HORIZON['dashboards'] 。是必选项。
DEFAULT ¶ New in version 2014.1(Icehouse).
如果设置为True, 这个dashboard将被设置为默认的dashboard。
例子:
若要在本地禁用dashboard,用下面内容创建一个 openstack_dashboard/local/enabled/_40_dashboard-name.py 文件: DASHBOARD = '' DISABLED = True
添加一个 Tuskar-UI(Infrastructure) 仪表板,首先必须安装它,然后创建一个文件 openstack_dashboard/local/enabled/_50_tuskar.py : from tuskar_ui import exceptions DASHBOARD = 'infrastructure' ADD_INSTALLED_APPS = [ 'tuskar_ui.infrastructure', ] ADD_EXCEPTIONS = { 'recoverable': exceptions.RECOVERABLE, 'not_found': exceptions.NOT_FOUND, 'unauthorized': exceptions.UNAUTHORIZED, }
Panels的插件化设置 ¶ New in version 2014.1(Icehouse).
下面的配置用于注册或删除panel。
PANEL ¶ New in version 2014.1(Icehouse).
此值为panel的slug字段值,会被添加到 HORIZON_CONFIG 。是必选项。
PANEL_DASHBOARD ¶ New in version 2014.1(Icehouse).
指定与 PANEL 相关的 dashboard的slug值。是必选项。此配置是将panel设为指定的dashbaord。
PANEL_GROUP ¶ New in version 2014.1(Icehouse).
指定与 PANEL 相关的panel组的slug值。此配置是将panel设为指定的组。 如果您希望此panel不在组里面,将此值设置为 “default”。
DEFAULT_PANEL ¶ New in version 2014.1(Icehouse).
如果设置此值,它将更新 PANEL_DASHBOARD 的默认panel。
ADD_PANEL ¶ New in version 2014.1(Icehouse).
此值为要添加的panel的Python类。
REMOVE_PANEL ¶ New in version 2014.1(Icehouse).
如果设置为True,panel将会从 PANEL_DASHBOARD或PANEL_GROUP移除。
例子:
要在Admin dashboard里的Admin panel group添加 一个panel,用下面内容创建一个 openstack_dashboard/local/enabled/_60_admin_add_panel.py 文件: PANEL = 'plugin_panel' PANEL_DASHBOARD = 'admin' PANEL_GROUP = 'admin' ADD_PANEL = 'test_panels.plugin_panel.panel.PluginPanel'
要将 Admin dashboard里的 Admin panel group的 Info panel 删除,用以下内容创建一个 openstack_dashboard/local/enabled/_70_admin_remove_panel.py 文件: PANEL = 'info' PANEL_DASHBOARD = 'admin' PANEL_GROUP = 'admin' REMOVE_PANEL = True
要将 Admin dashboard 里的默认panel改为 Instances,用下面内容创建一个 openstack_dashboard/local/enabled/_80_admin_default_panel.py 文件: PANEL = 'instances' PANEL_DASHBOARD = 'admin' PANEL_GROUP = 'admin' DEFAULT_PANEL = 'instances'
Panels组的插件化设置 ¶ New in version 2014.1(Icehouse).
下面的配置用于注册或删除panel组。
PANEL_GROUP ¶ New in version 2014.1(Icehouse).
此值为panel组的slug字段值,会被添加到 HORIZON_CONFIG 。是必选项。
PANEL_GROUP_NAME ¶ New in version 2014.1(Icehouse).
PANEL_GROUP 显示的名字。是必选项。
PANEL_GROUP_DASHBOARD ¶ New in version 2014.1(Icehouse).
指定与 PANEL_GROUP 相关的 dashboard的slug值。是必选项。此配置是将panel组设为指定的dashbaord。
例子:
添加一个新的panel组到 Admin dashboard,用以下内容创建 openstack_dashboard/local/enabled/_90_admin_add_panel_group.py : PANEL_GROUP = 'plugin_panel_group' PANEL_GROUP_NAME = 'Plugin Panel Group' PANEL_GROUP_DASHBOARD = 'admin'

转载自: http://luozn.top/2018/04/13/227/
云计算
2018-04-13 17:20:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
k8s 是当前主流的容器编排服务,它主要解决「集群环境」下「容器化应用」的「管理问题」,主要包括如下几方面:
  容器集群管理 • 编排 • 调度 • 访问   基础设施管理 • 计算资源 • 网络资源 • 存储资源 k8s 的强大依赖于它良好的设计理念和抽象,吸引了越来越多的开发者投入到 k8s 社区,把 k8s 作为基础设施运行服务的公司也逐步增多。
在设计理念方面,k8s 只有 APIServer 与 etcd (存储) 通信,其他组件在内存中维护状态,通过 APIServer 持久化数据。管理组件动作的触发是 level-based 而非 edge-based,并根据资源「当前状态」和「期望状态」进行相应动作。k8s 采用分层设计,基于各类抽象接口、由不同的插件满足不同的需求。
在抽象方面,不同的 workload 服务于不同的应用,如针对无状态应用的 Deployment、针对有状态应用的 StatefulSet 等。在访问管理方面,Service 解耦了集群内部服务访问方和提供者,Ingress 提供了集群外部到集群内部的访问管理。
k8s 虽然有良好的设计理念和抽象,但陡峭的学习曲线和不完善的开发资料极大增加了应用开发的难度。
本次分享将基于笔者的开发实践,以 MySQL on k8s 为例,描述如何基于 k8s 开发高可靠应用, 尽可能抽象出最佳实践,降低基于 k8s 开发高可靠应用的成本。
MySQL on k8s
应用的设计和开发不能脱离业务需求,对 MySQL 应用的需求如下: 数据高可靠 服务高可用 易使用 易运维
为了实现上述需求,需要依靠 k8s 和应用两方面协同工作,即开发基于 k8s 高可靠应用,既需要 k8s 相关的知识,也需要应用领域内的知识。
下述将根据上述需求来分析相应的解决方案。
1.数据高可靠
数据的高可靠一般依赖于这几方面: • 冗余 • 备份/恢复
我们使用 Percona XtraDB Cluster 作为 MySQL 集群方案,它是 multi-master 的 MySQL 架构,实例间基于 Galera Replication 技术实现数据的实时同步。 这种集群方案可以避免 master-slave 架构的集群在主从切换时可能出现的数据丢失现象,进一步提升数据的可靠性。
备份方面,我们使用 xtrabackup 作为备份/恢复方案,实现数据的热备份,在备份期间不影响用户对集群的正常访问。
提供「定时备份」的同时,我们也提供「手动备份」,以满足业务对备份数据的需求。
2.服务高可用
这里从「数据链路」和「控制链路」两个角度来分析。
「数据链路」是用户访问 MySQL 服务的链路,我们使用 3 主节点的 MySQL 集群方案,通过 TLB (七牛自研的四层负载均衡服务) 对用户提供访问入口。TLB 既实现了访问层面对 MySQL 实例的负载均衡,又实现了对服务的健康检测,自动摘除异常的节点,同时在节点恢复时自动加入该节点。如下图:
基于上述 MySQL 集群方案和 TLB, 一个或两个节点的异常不会影响用户对 MySQL 集群的正常访问,确保 MySQL 服务的高可用。
「控制链路」是 MySQL 集群的管理链路,分为两个层面: • 全局控制管理 • 每个 MySQL 集群的控制管理 全局控制管理主要负责「创建/删除集群」「管理所有 MySQL 集群状态」等,基于 Operator 的理念来实现。 每个 MySQL 集群有一个控制器,负责该集群的「任务调度」「健康检测」「故障自动处理」等。
这种拆解将每个集群的管理工作下放到每个集群中,降低了集群间控制链路的相互干扰,同时又减轻了全局控制器的压力。 如下图:
这里简单介绍下 Operator 的理念和实现。
Operator 是 CoreOS 公司提出的一个概念,用来创建、配置、管理复杂应用,由两部分构成:   Resource • 自定义资源 • 为用户提供一种简单的方式描述对服务的期望  Controller • 创建 Resource • 监听 Resource 的变更,用来实现用户对服务的期望
工作流程如下图所示:
即: 注册 CR (CustomResource) 资源 监听 CR objects 的变更 用户对该 CR 资源进行 CREATE/UPDATE/DELETE 操作 触发相应的 handler 进行处理
我们根据实践,对开发 Operator 做了如下抽象:
CR抽象为这样的结构体:
对 CR ADD/UPDATE/DELETE events 的操作,抽象为如下接口:
在上述抽象的基础上, 七牛提供了一个简单的 Operator 框架,透明化了创建 CR、监听 CR events 等的操作,将开发 Operator 的工作变的更为简单。
我们开发了 MySQL Operator 和 MySQL Data Operator, 分别用来负责「创建/删除集群」和「手动备份/恢复」工作。
由于每个 MySQL 集群会有多种类型的任务逻辑,如「数据备份」「数据恢复」「健康检测」「故障自动处理」等,这些逻辑的并发执行可能会引发异常,故需要任务调度器来协调任务的执行,Controller 起到的就是这方面的作用: 
通过 Controller 和各类 Worker,每个 MySQL 集群实现了自运维。
在「健康检测」方面,我们实现了两种机制: • 被动检测 • 主动检测 「被动检测」是每个 MySQL 实例向 Controller 汇报健康状态,「主动检测」是由 Controller 请求每个 MySQL 实例的健康状态。 这两种机制相互补充,提升健康检测的可靠度和及时性。
对于健康检测的数据,Controller 和 Operator 均会使用,如下图所示: 
Controller 使用健康检测数据是为了及时发现 MySQL 集群的异常,并做相应的故障处理,故需要准确、及时的健康状态信息。它在内存中维护所有 MySQL 实例的状态,根据「主动检测」和「被动检测」的结果更新实例状态并做相应的处理。
Operator 使用健康检测数据是为了向外界反映 MySQL 集群的运行情况,并在 Controller 异常时介入到 MySQL 集群的故障处理中。
在实践中,由于健康检测的频率相对较高,会产生大量的健康状态,若每个健康状态都被持久化,那么 Operator 和 APIServer 均会承受巨大的访问压力。由于这些健康状态仅最新的数据有意义, 故在 Controller 层面将待向 Operator 汇报的健康状态插入到一个有限容量的 Queue 中,当 Queue 满时,旧的健康状态将被丢弃。
当 Controller 检测到 MySQL 集群异常时,将会进行故障自动处理。
先定义故障处理原则: • 不丢数据 • 尽可能不影响可用性 • 对于已知的、能够处理的故障进行自动处理 • 对于未知的、不能够处理的故障不自动处理,人工介入 在故障处理中,有这些关键问题: • 故障类型有哪些 • 如何及时检测和感知故障 • 当前是否出现了故障 • 出现的故障是哪种故障类型 • 如何进行处理 针对上述关键问题,我们定义了 3 种级别的集群状态:
Green • 可以对外服务 • 运行节点数量符合预期 Yellow • 可以对外服务 • 运行节点数量不符合预期 Red • 不能对外服务
同时针对每个 mysqld 节点,定义了如下状态:
Green • 节点在运行 • 节点在 MySQL 集群中 Yellow • 节点在运行 • 节点不在 MySQL 集群中 Red-clean • 节点优雅退出 Red-unclean • 节点非优雅退出 Unknown • 节点状态不可知
Controller 收集到所有 MySQL 节点状态后,会根据这些节点的状态推算 MySQL 集群的状态。当检测到 MySQL 集群状态不是 Green 时,会触发「故障处理」逻辑,该逻辑会根据已知的故障处理方案进行处理。若该故障类型未知,人工介入处理。整个流程如下图:
由于每种应用的故障场景和处理方案不同,这里不再叙述具体的处理方法。
3.易使用
我们基于 Operator 的理念实现了高可靠的 MySQL 服务,为用户定义了两类资源,即 QiniuMySQL 和 QiniuMySQLData。前者描述用户对 MySQL 集群的配置,后者描述手动备份/恢复数据的任务,这里以 QiniuMySQL 为例。
用户可通过如下简单的 yaml 文件触发创建 MySQL 集群的操作:
在集群创建好后,用户可通过该 CR object 的 status 字段获取集群状态:
这里再引入一个概念:Helm。
Helm 是为 k8s 提供的包管理工具,通过将应用打包为 Chart,标准化了 k8s 应用的交付、部署和使用流程。
Chart 本质上是 k8s yaml 文件和参数文件的集合,这样可以通过一个 Chart 文件进行应用的交付。Helm 通过操作 Chart,可一键部署、升级应用。
由于篇幅原因及 Helm 操作的通用性,这里不再描述具体的使用过程。
4.易运维
除了上述实现的「健康检测」「故障自动处理」以及通过 Helm 管理应用的交付、部署,在运维过程中还有如下问题需要考虑: • 监控/告警 • 日志管理
我们通过 prometheus + grafana 做监控/告警服务,服务将 metric 数据以 HTTP API 暴露给 prometheus,由 prometheus server 定时拉取。开发人员在 grafana 上将 prometheus 中的监控数据可视化,根据对监控图表和应用的把握,在监控图中设置告警线,由 grafana 实现告警。
这种先可视化监控后告警的方式,极大程度上增强了我们对应用运行特征的把握, 明确需要关注的指标和告警线,降低无效告警的产生量。
在开发中,我们通过 gRPC 实现服务间的通信。在 gRPC 生态系统中,有个名为 go-grpc-prometheus 的开源项目,通过在服务中插入几行简单的代码,就可以实现对 gRPC server 所有 rpc 请求的监控打点。
对于容器化服务,日志管理包括「日志收集」和「日志滚动」两方面维度。
我们将服务日志打到 syslog 中,再通过某种手段将 syslog 日志转入到容器的 stdout/stderr 中,方便外部采用常规的方式进行日志收集。同时,在 syslog 中配置了 logrotate 功能,自动进行日志的滚动操作,避免日志占满容器磁盘空间引发服务异常。
为了提升开发效率,我们使用  https://github.com/phusion/baseimage-docker  作为基础镜像,其中内置了 syslog 和 lograte 服务,应用只关心把日志打入 syslog 即可,不用关心日志的收集和日志滚动问题。
小结
通过上述描述,完整的 MySQL 应用架构如下:
在开发基于 k8s 的高可靠 MySQL 应用过程中,随着对 k8s 和 MySQL 理解的深入,我们不断进行抽象,逐步将如下通用的逻辑和最佳实践以模块的方式实现: • Operator 开发框架 • 健康检测服务 • 故障自动处理服务 • 任务调度服务 • 配置管理服务 • 监控服务 • 日志服务 • etc.
随着这些通用逻辑和最佳实践的模块化,在开发新的基于 k8s 的高可靠应用时,开发者可像「搭积木」一样将与 k8s 相关的交互快速搭建起来,这样的应用由于已经运用了最佳实践,从一开始就具备高可靠的特性。 同时,开发者可将注意力从 k8s 陡峭的学习曲线转移到应用自身领域,从应用自身加强服务的可靠性。
牛人说
牛人说专栏致力于技术人思想的发现,其中包括技术实践、技术干货、技术见解、成长心得,还有一切值得被发现的技术内容。我们希望集合最优秀的技术人,挖掘独到、犀利、具有时代感的声音。
云计算
2018-04-13 13:42:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
本节演示如何为 MySQL 数据库提供持久化存储,步骤为: 创建 PV 和 PVC。 部署 MySQL。 向 MySQL 添加数据。 模拟节点宕机故障,Kubernetes 将 MySQL 自动迁移到其他节点。 验证数据一致性。
首先创建 PV 和 PVC,配置如下:
mysql-pv.yml
mysql-pvc.yml
创建 mysql-pv 和 mysql-pvc :
接下来部署 MySQL,配置文件如下:
PVC mysql-pvc Bound 的 PV mysql-pv 将被 mount 到 MySQL 的数据目录 var/lib/mysql 。
MySQL 被部署到 k8s-node2 ,下面通过客户端访问 Service mysql : kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -ppassword
更新数据库:
① 切换到数据库 mysql。
② 创建数据库表 my_id。
③ 插入一条数据。
④ 确认数据已经写入。
关闭 k8s-node2 ,模拟节点宕机故障。
一段时间后,Kubernetes 将 MySQL 迁移到 k8s-node1 。
验证数据的一致性:
MySQL 服务恢复,数据也完好无损。
小结
本章我们讨论了 Kubernetes 如何管理存储资源。
emptyDir 和 hostPath 类型的 Volume 很方便,但可持久性不强,Kubernetes 支持多种外部存储系统的 Volume。
PV 和 PVC 分离了管理员和普通用户的职责,更适合生产环境。我们还学习了如何通过 StorageClass 实现更高效的动态供给。
最后,我们演示了如何在 MySQL 中使用 PersistentVolume 实现数据持久性。
书籍:
1.《每天5分钟玩转Kubernetes》
https://item.jd.com/26225745440.html
2.《每天5分钟玩转Docker容器技术》
https://item.jd.com/16936307278.html
3.《每天5分钟玩转OpenStack》
https://item.jd.com/12086376.html
云计算
2018-04-13 07:03:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
你听说过Service Mesh(服务网格)吗?
我相信你听说过。Service Mesh正成为容器生态圈愈发重要的一部分。
本文将简要概述Service Mesh的作用,并深入探讨它们对于企业安全性的意义。
Service Meshes是什么?它为何如此重要?
连接问题
要想理解Service Mesh的存在原因,首先考虑一下容器环境中的网络连接。
想象一下当你运行一个云原生应用时会发生什么。但凡它具有一定的规模和复杂性,它通常都需要由大量单独的服务组成,这些服务间为了能够像一个单体桌面应用程序组件一样高效地运行,需要相互进行协调。
再加上在任何指定时间运行的每个服务的实例数量,以及这些实例的状态及可用性的变化,不难看出,简单的将一个服务连接到另一个服务的行为,在这样的情形下会变成多么恐怖的组合问题。
编排是基础
多亏了有Kubernetes等编排工具,云原生应用程序不会出现混乱或者从内部logjams出现冻结,它们将服务和实例组织成方便管理和寻址的单元,这样就可以通过系统化的方式找到并访问这些单元。
这些编排工具就好像一个房屋开发商,他们铺设街道,在新的社区建造房屋——它们建立了框架和交通线路,然而大多数情况下,处理社区的交通细节并不是它们的工作。
管理流量
这就是需要Service Mesh的地方。当一个服务需要向另一个服务发送请求时,Service Mesh提供了一个标准化的接口,允许请求发送,并且管理这一个过程。
Service Mesh(如lstio和Linknerd)通常充当微服务之间的请求和其他流量的代理,负责服务发现和执行各种相关任务,包括入口、出口、负载均衡和故障处理。当它接收到服务请求时,它会找到一个服务的可用实例,这个实例需满足一组可配置的规则以及在请求服务和目标服务之间的路由流量。
接手复杂的工作
这意味着你可以将服务发现和与其关联的大多数任务从应用程序设计和代码(以及架构脚本)中移出来,并交给Service Mesh处理它们。请求服务只需要使用目标服务的抽象标识符来提出请求;而Service Mesh将负责剩下的部分。
当然,Service Mesh能够处理的不止这些,还包括了跟踪、度量、加密、认证以及其他与性能和安全相关的任务。lstio和linkerd可以一同使用,把两者包中最强大的特性集成在一起,进行微服务相关的流量管理优化。
Service Mesh与企业安全
上文所说的这一切,对于企业安全意味着什么呢?
Istio和Linkerd等平台的安全性和整体流量管理功能是否能够提供足够的保护?或者恰恰相反,他们是否会暴露出新的攻击面,给入侵者提供了后门攻击的机会?
事实是,任何控制基础设施的新元素都有可能包含这两者。在Service Mesh的情景下,如入口/出口管理、代理和加密等特性将与安全相关的元素添加到系统中。与此同时,这些平台会管理流量和访问,并且受到应用程序和其他基础设施元素的信任,这使得它们成为了攻击目标。
Service Mesh的整体作用是在应用程序的边界(即入口规则)处提供一些强化,并为该周边内的流量创建有效的通道。就企业安全而言,这意味着你需要关注至少两条(或许更多)的潜在攻击路线。
当入侵者突破边界
如果入侵者突破Service Mesh的基本边界防御,并且甚至侵占了一个服务的某一个实例,将会发生什么?如果该服务向Service Mesh发送请求,或它响应Service Mesh的请求,那么恶意负载可能会被注入系统中,从而利用Service Mesh的高效流量管理将负载传递到尽可能多的潜在目标。如果Service Mesh将服务表现出来的样子认定为“信任”,而应用程序又错误地判定Service Mesh在服务之间传递的是非恶意数据,那么任何的恶意参与者都可以利用这种信任,将自己伪装成有效服务。
当然,实际上像Istio和Linkerd这样的平台确实包含了维护安全流量的功能,比如TLC认证;Istio的Role-Based Access Control(基于角色的访问控制,RBAC)提供了灵活、可定制的多级访问控制。但是,如果入侵者突破了这些防御,仍然可以在系统内移动并且造成破坏。
攻击Service Mesh基础设施
Service Mesh平台,就像当下其他任何基于云基础架构的元素一样,是由代码构成的,并且和其他类型的代码一样容易受到攻击。对入侵者来说,最具诱惑力的攻击面可能是控制发现和路由的规则——如果请求可以重新路由到外部位置,那么整个系统都可能会受到危害。
当然也会有其他的攻击点。入口、出口、代理以及负载均衡等功能都可能会出现先前未监测到的切入点。简而言之就是,基础设施的元素越多地控制应用程序和整个系统,那么它就越容易成为攻击的目标,也就越应该关注它。
抵御攻击
那么有没有最佳策略来处理与Service Mesh有关的安全性问题?有的。
Service Mesh本身提供的防御功能可以和像白名单这样强大的边界防御工作结合起来使用,进一步加强应用程序的入侵防御能力。其中,内部异常检测提供了更强大的防御。程序中任何不寻常的行为都会触发自动响应。网络安全监控可以检测并抵消掉服务网格基础设施本身受到的攻击。
在基于云的领域中,容器化应用程序、Service Mesh都是企业计算中不可或缺的工具。与功能齐全的企业级安全服务结合使用,入侵者就没有必要,也不会损害到组织的数据安全。
云计算
2018-04-12 17:57:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
环境:openstack(ocata版本)在docker 容器里运行ceilomter-api服务,部署完api的服务就无法正常使用ceilomter 命令(ceilometer meter-list)
报错日志:
解决方法:
编辑ceilometer-api/wsgi-ceilometer-api.conf
在WSGIProcessGroup ceilometer-api下面添加 WSGIApplicationGroup %{GLOBAL}
然后重启ceilomter-api服务
云计算
2018-04-11 19:37:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
#cd /usr/local/
#mkdir flume
#cd flume
下载压缩包
#wget http://archive.cloudera.com/cdh5/cdh/5/flume-ng-1.6.0-cdh5.7.1.tar.gz
解压
#tar zxvf flume-ng-1.6.0-cdh5.7.1.tar.gz
配置环境变量:
#vim /ect/profile
export FLUME_HOME=/home/hadoop/app/cdh/flume-1.6.0-cdh5.7.1
export PATH=$PATH:$FLUME_HOME/bin
版本验证:
#flume-ng version
cd /usr/local/flume/example/
# cp /usr/local/flume/conf/flume-conf.properties.template /usr/local/flume/example/netcat.conf
// 配置netcat.conf用于实时获取另一终端输入的数据
# vim example/netcat.conf
# Name the components on this agent
a1.sources = r1
a1.sinks = k1
a1.channels = c1
# Describe/configure the source
a1.sources.r1.type = netcat
a1.sources.r1.bind = localhost
a1.sources.r1.port = 44444
# Describe the sink
a1.sinks.k1.type = logger
# Use a channel that buffers events in memory
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100
# Bind the source and sink to the channel
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
// 运行FlumeAgent,监听本机的44444端口
flume-ng agent -c /usr/local/flume/conf/ -f /usr/local/flume/example/netcat.conf -n a1 -Dflume.root.logger=INFO,console
// 打开另一终端,通过telnet登录localhost的44444,输入测试数据
#telnet localhost 44444
// 查看flume收集数据情况
云计算
2018-04-11 15:55:09
「深度学习福利」大神带你进阶工程师,立即查看>>>
Thom Crowe
技术圈里从来都不缺少热门的解决方案,而作为开发者,我们也总是被酷炫的新技术所吸引,期望这些新思路能够帮助我们应对技术挑战和业务问题。
然而问题在于,新技术成熟并获得广泛应用之前,我们很难判断该技术是否真的是我们用例的最佳选择。
因此一项技术成为主流,进而成为未来标准,本身不是一件坏事——虽然失去了一丝探索各类可能(和“坑”)的兴奋感,但我们获得了可靠性和便捷度。
随着技术的成熟,更简单的APIs、更丰富的libraries、更广泛的社区支持,乃至培训、认证等技术相关事物也随之而生。成熟的技术让我们可以有更多时间,专注于解决高级问题,而不是跟复杂的技术概念和底层设施较劲,换句话说,我们没必要再去重复尝试获取最佳实践的种种细节了。
一个典型的例子是Kubernetes,这项从诞生之日起便备受瞩目的技术,近来似乎已经越过了主流与否的临界点。
Kubernetes的“飞跃”
今年早些时候,Kubernetes成为了第一个从CNCF孵化器毕业的项目。专家们普遍认为,K8s的”飞跃“得益于以下三点——
首先,Docker在2017年初将其容器技术捐献给CNCF,Docker容器平台核心runtime变成了一个社区所有项目,有助于建立容器化的行业标准。
其次,世界上主要的云服务商和IT供应商不约而同选择了Kubernetes作为默认的容器编排工具,他们中很多已经推出了自己的 Kubernetes解决方案 、平台和云计算托管服务。
第三,CNCF推出了Kubernetes一致性认证计划,帮助供应商证明他们的解决方案符合Kubernetes标准——让客户相信他们可以采用Kubernetes而不会被单一供应商锁定。
“无聊”即生意
如果说Kubernetes已经进入了“认证”和“最佳实践”的世界,是否意味着它已经不再是一个热门的新兴技术了?或者换句话说:Kubernetes会变得很“无聊”吗?
很多专家认为,所谓的“无聊”不是坏事。Kubernetes已经赢得了容器编排爱好者的心,但为了在更广阔的世界中生存下去,它需要吸引那些不会花太多时间来学习Kubernetes复杂技术概念的人,而这些人恰恰是会在未来几年中利用K8s来解决实际业务问题的人——对于这些用户来说,能够快速落地技术并使技术正常工作至关重要。
这就是为什么CNCF正在着手建立的一系列培训和教育体系,不仅仅是针对Kubernetes专家,还面向广泛的开发人员。如果我们只是想要利用Kubernetes来编排容器,会使用便已足够。
“无聊”即简单
除了认证和培训之外,各云计算服务商所提供的,越来越多的托管服务以及简单易懂不需要特殊学习的Kubernetes解决方案,也让Kubernetes的应用变得越来越广泛。
作为开发者,我们的第一要务是构建应用并让应用跑起来。我们可能最终会成为精通K8s的专家,但这恐怕不是我们第一天就要着手做的事情。云计算服务商所提供的方案、工具和平台作用正在于此,我们不必从一开就担心如何配置kubernetes、如何维护、如何优化等等问题。
不仅仅是云原生应用
Kubernetes不仅仅适用于新的云原生项目,它还可以应用在对传统应用的现代化改造上。通过对遗留应用的容器化改造以及Kubernetes的编排,我们可以打破原有应用架构的局限性,转变为架构适应应用需求的全新模式。
经改造过的应用程序,能够充分利用诸如弹性、可扩展性等云原生优势,以及强大的路由、日志、监控和安全工具,同时我们可以在其之上添加新的微服务来实现扩展功能。
探索新的“兴奋”
如果Kubernetes已经变得很成熟,已经是一种非常优秀的解决方案,喜欢“鼓捣”的开发者应该去哪寻找新的“兴奋点”?
想想Docker容器化解决方案的普及为Kubernetes铺平了发展的道路,Kubernetes的发展解锁了Service Mesh微服务架构……也许随着越来越多组织将Kubernetes投入生产,连接、管理、保护微服务的能力将成为下一个需要解决的挑战。
好雨Rainbond (云帮)是一款以应用为中心的开源PaaS,深度整合基于Kubernetes的容器管理、Service Mesh微服务架构最佳实践、多类型CI/CD应用构建与交付、多数据中心资源管理等技术,为用户提供云原生应用全生命周期解决方案,构建应用与基础设施、应用与应用、基础设施与基础设施之间互联互通的生态体系,满足支撑业务高速发展所需的敏捷开发、高效运维和精益管理需求。
云计算
2018-04-11 14:51:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
前面的例子中,我们提前创建了 PV,然后通过 PVC 申请 PV 并在 Pod 中使用,这种方式叫做静态供给(Static Provision)。
与之对应的是动态供给(Dynamical Provision),即如果没有满足 PVC 条件的 PV,会动态创建 PV。相比静态供给,动态供给有明显的优势:不需要提前创建 PV,减少了管理员的工作量,效率高。
动态供给是通过 StorageClass 实现的,StorageClass 定义了如何创建 PV,下面是两个例子。
StorageClass standard :
StorageClass slow :
这两个 StorageClass 都会动态创建 AWS EBS,不同在于 standard 创建的是 gp2 类型的 EBS,而 slow 创建的是 io1 类型的 EBS。不同类型的 EBS 支持的参数可参考 AWS 官方文档。
StorageClass 支持 Delete 和 Retain 两种 reclaimPolicy ,默认是 Delete 。
与之前一样,PVC 在申请 PV 时,只需要指定 StorageClass 和容量以及访问模式,比如:
除了 AWS EBS,Kubernetes 支持其他多种动态供给 PV 的 Provisioner,完整列表请参考 https://kubernetes.io/docs/concepts/storage/storage-classes/#provisioner
下一节以 MySQL 为例演示如何在项目中使用 PV 和 PVC。
书籍:
1.《每天5分钟玩转Kubernetes》
https://item.jd.com/26225745440.html
2.《每天5分钟玩转Docker容器技术》
https://item.jd.com/16936307278.html
3.《每天5分钟玩转OpenStack》
https://item.jd.com/12086376.html
云计算
2018-04-11 06:22:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Device Plugins
Device Pulgins在Kubernetes 1.10中是beta特性,开始于Kubernetes 1.8,用来给第三方设备厂商通过插件化的方式将设备资源对接到Kubernetes,给容器提供Extended Resources。
通过Device Plugins方式,用户不需要改Kubernetes的代码,由第三方设备厂商开发插件,实现Kubernetes Device Plugins的相关接口即可。
目前关注度比较高的Device Plugins实现有: Nvidia提供的GPU插件: NVIDIA device plugin for Kubernetes 高性能低延迟RDMA卡插件: RDMA device plugin for Kubernetes 低延迟Solarflare万兆网卡驱动: Solarflare Device Plugin
Device plugins启动时,对外暴露几个gRPC Service提供服务,并通过 /var/lib/kubelet/device-plugins/kubelet.sock 向kubelet进行注册。
Device Plugins Registration 在Kubernetes 1.10之前的版本,默认disable DevicePlugins,用户需要在Feature Gate中enable。 在Kubernetes 1.10,默认enable DevicePlugins,用户可以在Feature Gate中disable it。 当DevicePlugins Feature Gate enable,kubelet就会暴露一个Register gRPC接口。Device Plugins通过调用Register接口完成Device的注册。 Register接口描述如下: pkg/kubelet/apis/deviceplugin/v1beta1/api.pb.go:440 type RegistrationServer interface { Register(context.Context, *RegisterRequest) (*Empty, error) } pkg/kubelet/apis/deviceplugin/v1beta1/api.pb.go:87 type RegisterRequest struct { // Version of the API the Device Plugin was built against Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` // Name of the unix socket the device plugin is listening on // PATH = path.Join(DevicePluginPath, endpoint) Endpoint string `protobuf:"bytes,2,opt,name=endpoint,proto3" json:"endpoint,omitempty"` // Schedulable resource name. As of now it's expected to be a DNS Label ResourceName string `protobuf:"bytes,3,opt,name=resource_name,json=resourceName,proto3" json:"resource_name,omitempty"` // Options to be communicated with Device Manager Options *DevicePluginOptions `protobuf:"bytes,4,opt,name=options" json:"options,omitempty"` } RegisterRequest要求的参数如下: Version, 目前有v1alpha,v1beta1两个版本。 Endpoint, 表示device plugin暴露的socket名称,Register时会根据Endpoint生成plugin的socket放在 /var/lib/kubelet/device-plugins/ 目录下,比如Nvidia GPU Device Plugin对应 /var/lib/kubelet/device-plugins/nvidia.sock 。 ResourceName, 须按照Extended Resource Naming Scheme格式 vendor-domain/resource ,比如 nvidia.com/gpu DevicePluginOptions, 作为kubelet与device plugin通信时的额外参数传递。 对于nvidia gpu,只有一个PreStartRequired选项,表示每个Container启动前是否要调用Device Plugin的PreStartContainer接口(是Kubernetes 1.10中Device Plugin Interface接口之一),默认为false。 vendor/k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1beta1/api.pb.go:71 func (m *NvidiaDevicePlugin) GetDevicePluginOptions(context.Context, *pluginapi.Empty) (*pluginapi.DevicePluginOptions, error) { return &pluginapi.DevicePluginOptions{}, nil } github.com/NVIDIA/k8s-device-plugin/server.go:80 type DevicePluginOptions struct { // Indicates if PreStartContainer call is required before each container start PreStartRequired bool `protobuf:"varint,1,opt,name=pre_start_required,json=preStartRequired,proto3" json:"pre_start_required,omitempty"` } 前面提到Device Plugin Interface目前有v1alpha, v1beta1两个版本,每个版本对应的接口如下: v1alpha : /deviceplugin.Registration/ Register pkg/kubelet/apis/deviceplugin/v1alpha/api.pb.go:374 var _Registration_serviceDesc = grpc.ServiceDesc{ ServiceName: "deviceplugin.Registration", HandlerType: (*RegistrationServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "Register", Handler: _Registration_Register_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "api.proto", } /deviceplugin.DevicePlugin/ Allocate /deviceplugin.DevicePlugin/ ListAndWatch pkg/kubelet/apis/deviceplugin/v1alpha/api.pb.go:505 var _DevicePlugin_serviceDesc = grpc.ServiceDesc{ ServiceName: "deviceplugin.DevicePlugin", HandlerType: (*DevicePluginServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "Allocate", Handler: _DevicePlugin_Allocate_Handler, }, }, Streams: []grpc.StreamDesc{ { StreamName: "ListAndWatch", Handler: _DevicePlugin_ListAndWatch_Handler, ServerStreams: true, }, }, Metadata: "api.proto", } v1beta1 : /v1beta1.Registration/ Register /v1beta1.Registration/Register pkg/kubelet/apis/deviceplugin/v1beta1/api.pb.go:466 var _Registration_serviceDesc = grpc.ServiceDesc{ ServiceName: "v1beta1.Registration", HandlerType: (*RegistrationServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "Register", Handler: _Registration_Register_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "api.proto", } /v1beta1.DevicePlugin/ ListAndWatch /v1beta1.DevicePlugin/ Allocate /v1beta1.DevicePlugin/ PreStartContainer /v1beta1.DevicePlugin/ GetDevicePluginOptions pkg/kubelet/apis/deviceplugin/v1beta1/api.pb.go:665 var _DevicePlugin_serviceDesc = grpc.ServiceDesc{ ServiceName: "v1beta1.DevicePlugin", HandlerType: (*DevicePluginServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "GetDevicePluginOptions", Handler: _DevicePlugin_GetDevicePluginOptions_Handler, }, { MethodName: "Allocate", Handler: _DevicePlugin_Allocate_Handler, }, { MethodName: "PreStartContainer", Handler: _DevicePlugin_PreStartContainer_Handler, }, }, Streams: []grpc.StreamDesc{ { StreamName: "ListAndWatch", Handler: _DevicePlugin_ListAndWatch_Handler, ServerStreams: true, }, }, Metadata: "api.proto", } 当Device Plugin成功注册后,它将通过ListAndWatch向kubelet发送它管理的device列表,kubelet收到数据后通过API Server更新etcd中对应node的status中。 然后用户就能在Container Spec request中请求对应的device,注意以下限制: Extended Resource只支持请求整数个device,不支持小数点。 不支持超配,即Resource QoS只能是Guaranteed。 同一块Device不能多个Containers共享。
Device Plugins Workflow
Device Plugins的工作流如下: 初始化 :Device Plugin启动后,进行一些插件特定的初始化工作以确定对应的Devices处于Ready状态,对于Nvidia GPU,就是加载NVML Library。 启动gRPC服务 :通过 /var/lib/kubelet/device-plugins/${Endpoint}.sock 对外暴露gRPC服务,不同的API Version对应不同的服务接口,前面已经提过,下面是每个接口的描述。 v1alpha : ListAndWatch Allocate pkg/kubelet/apis/deviceplugin/v1alpha/api.proto // DevicePlugin is the service advertised by Device Plugins service DevicePlugin { // ListAndWatch returns a stream of List of Devices // Whenever a Device state changes or a Device disappears, ListAndWatch // returns the new list rpc ListAndWatch(Empty) returns (stream ListAndWatchResponse) {} // Allocate is called during container creation so that the Device // Plugin can run device specific operations and instruct Kubelet // of the steps to make the Device available in the container rpc Allocate(AllocateRequest) returns (AllocateResponse) {} } v1beta1 : ListAndWatch Allocate GetDevicePluginOptions PreStartContainer pkg/kubelet/apis/deviceplugin/v1beta1/api.proto // DevicePlugin is the service advertised by Device Plugins service DevicePlugin { // GetDevicePluginOptions returns options to be communicated with Device // Manager rpc GetDevicePluginOptions(Empty) returns (DevicePluginOptions) {} // ListAndWatch returns a stream of List of Devices // Whenever a Device state change or a Device disapears, ListAndWatch // returns the new list rpc ListAndWatch(Empty) returns (stream ListAndWatchResponse) {} // Allocate is called during container creation so that the Device // Plugin can run device specific operations and instruct Kubelet // of the steps to make the Device available in the container rpc Allocate(AllocateRequest) returns (AllocateResponse) {} // PreStartContainer is called, if indicated by Device Plugin during registeration phase, // before each container start. Device plugin can run device specific operations // such as reseting the device before making devices available to the container rpc PreStartContainer(PreStartContainerRequest) returns (PreStartContainerResponse) {} } Device Plugin通过 /var/lib/kubelet/device-plugins/kubelet.sock 向kubelet进行注册。 注册成功后,Device Plugin就正式进入了Serving模式,提供前面提到的gRPC接口调用服务,下面是v1beta1的每个接口对应的具体分析: ListAndWatch :监控对应Devices的状态变更或者Disappear事件,返回 ListAndWatchResponse 给kubelet, ListAndWatchResponse就是Device列表。 type ListAndWatchResponse struct { Devices []*Device `protobuf:"bytes,1,rep,name=devices" json:"devices,omitempty"` } type Device struct { // A unique ID assigned by the device plugin used // to identify devices during the communication // Max length of this field is 63 characters ID string `protobuf:"bytes,1,opt,name=ID,json=iD,proto3" json:"ID,omitempty"` // Health of the device, can be healthy or unhealthy, see constants.go Health string `protobuf:"bytes,2,opt,name=health,proto3" json:"health,omitempty"` }
下面是 struct Device 的GPU Sample: struct Device { ID: "GPU-fef8089b-4820-abfc-e83e-94318197576e", State: "Healthy", } Allocate :Device Plugin执行device-specific操作,返回 AllocateResponse 给kubelet,kubelet再传给dockerd,由dockerd(调用nvidia-docker)在创建容器时分配device时使用。下面是这个接口的Request和Response的描述。 Allocate is expected to be called during pod creation since allocation failures for any container would result in pod startup failure. Allocate allows kubelet to exposes additional artifacts in a pod's environment as directed by the plugin. Allocate allows Device Plugin to run device specific operations on the Devices requested type AllocateRequest struct { ContainerRequests []*ContainerAllocateRequest `protobuf:"bytes,1,rep,name=container_requests,json=containerRequests" json:"container_requests,omitempty"` } type ContainerAllocateRequest struct { DevicesIDs []string `protobuf:"bytes,1,rep,name=devicesIDs" json:"devicesIDs,omitempty"` } // AllocateResponse includes the artifacts that needs to be injected into // a container for accessing 'deviceIDs' that were mentioned as part of // 'AllocateRequest'. // Failure Handling: // if Kubelet sends an allocation request for dev1 and dev2. // Allocation on dev1 succeeds but allocation on dev2 fails. // The Device plugin should send a ListAndWatch update and fail the // Allocation request type AllocateResponse struct { ContainerResponses []*ContainerAllocateResponse `protobuf:"bytes,1,rep,name=container_responses,json=containerResponses" json:"container_responses,omitempty"` } type ContainerAllocateResponse struct { // List of environment variable to be set in the container to access one of more devices. Envs map[string]string `protobuf:"bytes,1,rep,name=envs" json:"envs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // Mounts for the container. Mounts []*Mount `protobuf:"bytes,2,rep,name=mounts" json:"mounts,omitempty"` // Devices for the container. Devices []*DeviceSpec `protobuf:"bytes,3,rep,name=devices" json:"devices,omitempty"` // Container annotations to pass to the container runtime Annotations map[string]string `protobuf:"bytes,4,rep,name=annotations" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } // DeviceSpec specifies a host device to mount into a container. type DeviceSpec struct { // Path of the device within the container. ContainerPath string `protobuf:"bytes,1,opt,name=container_path,json=containerPath,proto3" json:"container_path,omitempty"` // Path of the device on the host. HostPath string `protobuf:"bytes,2,opt,name=host_path,json=hostPath,proto3" json:"host_path,omitempty"` // Cgroups permissions of the device, candidates are one or more of // * r - allows container to read from the specified device. // * w - allows container to write to the specified device. // * m - allows container to create device files that do not yet exist. Permissions string `protobuf:"bytes,3,opt,name=permissions,proto3" json:"permissions,omitempty"` } AllocateRequest就是DeviceID列表。 AllocateResponse包括需要注入到Container里面的Envs、Devices的挂载信息(包括device的cgroup permissions)以及自定义的Annotations。 PreStartContainer : PreStartContainer is expected to be called before each container start if indicated by plugin during registration phase. PreStartContainer allows kubelet to pass reinitialized devices to containers. PreStartContainer allows Device Plugin to run device specific operations on the Devices requested. type PreStartContainerRequest struct { DevicesIDs []string `protobuf:"bytes,1,rep,name=devicesIDs" json:"devicesIDs,omitempty"` } // PreStartContainerResponse will be send by plugin in response to PreStartContainerRequest type PreStartContainerResponse struct { } GetDevicePluginOptions : 目前只有 PreStartRequired 这一个field。 type DevicePluginOptions struct { // Indicates if PreStartContainer call is required before each container start PreStartRequired bool `protobuf:"varint,1,opt,name=pre_start_required,json=preStartRequired,proto3" json:"pre_start_required,omitempty"` }
异常处理 每次kubelet启动(重启)时,都会将/var/lib/kubelet/device-plugins下的所有sockets文件删除。 Device Plugin要负责监测自己的socket被删除,然后进行重新注册,重新生成自己的socket。 当plugin socket被误删,Device Plugin该怎么办?
我们看看Nvidia Device Plugin是怎么处理的,相关的代码如下: github.com/NVIDIA/k8s-device-plugin/main.go:15 func main() { ... log.Println("Starting FS watcher.") watcher, err := newFSWatcher(pluginapi.DevicePluginPath) ... restart := true var devicePlugin *NvidiaDevicePlugin L: for { if restart { if devicePlugin != nil { devicePlugin.Stop() } devicePlugin = NewNvidiaDevicePlugin() if err := devicePlugin.Serve(); err != nil { log.Println("Could not contact Kubelet, retrying. Did you enable the device plugin feature gate?") log.Printf("You can check the prerequisites at: https://github.com/NVIDIA/k8s-device-plugin#prerequisites") log.Printf("You can learn how to set the runtime at: https://github.com/NVIDIA/k8s-device-plugin#quick-start") } else { restart = false } } select { case event := <-watcher.Events: if event.Name == pluginapi.KubeletSocket && event.Op&fsnotify.Create == fsnotify.Create { log.Printf("inotify: %s created, restarting.", pluginapi.KubeletSocket) restart = true } case err := <-watcher.Errors: log.Printf("inotify: %s", err) case s := <-sigs: switch s { case syscall.SIGHUP: log.Println("Received SIGHUP, restarting.") restart = true default: log.Printf("Received signal \"%v\", shutting down.", s) devicePlugin.Stop() break L } } } } 通过 fsnotify.Watcher 监控 /var/lib/kubelet/device-plugins/ 目录。 如果 fsnotify.Watcher 的Events Channel收到Create kubelet.sock 事件(说明kubelet发生重启),则会触发Nvidia Device Plugin的重启。 Nvidia Device Plugin重启的逻辑是:先检查devicePlugin对象是否为空(说明完成了Nvidia Device Plugin的初始化): 如果不为空,则先停止Nvidia Device Plugin的gRPC Server。 然后调用NewNvidiaDevicePlugin()重建一个新的DevicePlugin实例。 调用Serve()启动gRPC Server,并先kubelet注册自己。
因此,这其中只监控了 kubelet.sock 的Create事件,能很好处理kubelet重启的问题,但是并没有监控自己的socket是否被删除的事件。所以,如果Nvidia Device Plugin的socket被误删了,那么将会导致kubelet无法与该节点的Nvidia Device Plugin进行socket通信,则意味着Device Plugin的gRPC接口都无法调通: 无法ListAndWatch该节点上的Device列表、健康状态,Devices信息无法同步。 无法Allocate Device,导致容器创建失败。
因此,建议加上对自己device plugin socket的删除事件的监控,一旦监控到删除,则应该触发restart。 select { case event := <-watcher.Events: if event.Name == pluginapi.KubeletSocket && event.Op&fsnotify.Create == fsnotify.Create { log.Printf("inotify: %s created, restarting.", pluginapi.KubeletSocket) restart = true } // 增加对nvidia.sock的删除事件监控 if event.Name == serverSocket && event.Op&fsnotify.Delete == fsnotify.Delete { log.Printf("inotify: %s deleted, restarting.", serverSocket) restart = true } ... }
Extended Resources Device Plugin是通过Extended Resources来expose宿主机上的资源的,Kubernetes内置的Resources都是隶属于 kubernetes.io domain的,因此Extended Resource不允许advertise在 kubernetes.io domain下。 Node-level Extended Resource Device plugin管理的资源 其他资源 给API Server提交PATCH请求,给node的status.capacity添加新的资源名称和数量; kubelet通过定期更新node status.allocatable到API Server,这其中就包括事先给node打PATCH新加的资源。之后请求了新加资源的Pod就会被scheduler根据node status.allocatable进行FitResources Predicate甩选node。 注意:kubelet通过--node-status-update-frequency配置定期更新间隔,默认10s。因此,当你提交完PATCH后,最坏情况下可能要等待10s左右的时间才能被scheduler发现并使用该资源。 curl --header "Content-Type: application/json-patch+json" \ --request PATCH \ --data '[{"op": "add", "path": "/status/capacity/example.com~1foo", "value": "5"}]' \ http://k8s-master:8080/api/v1/nodes/k8s-node-1/status 注意:~1 is the encoding for the character / in the patch path。 Cluster-level Extended Resources 通常集群级的Extended Resources是给scheduler extender使用的,用来做Resources的配额管理。 当Pod请求的resource中包含该extended resources时,default scheduler才会将这个Pod发给对应的scheduler extender进行二次调度。 ignoredByScheduler field如果设置为true,则default scheduler将不会对该资源进行PodFitsResources预选检查,通常都会设置为true,因为Cluster-level不是跟node相关的,不适合进行PodFitResources对Node资源进行检查。 { "kind": "Policy", "apiVersion": "v1", "extenders": [ { "urlPrefix":"", "bindVerb": "bind", "ManagedResources": [ { "name": "example.com/foo", "ignoredByScheduler": true } ] } ] } API Server限制了Extender Resources只能为整数,比如2,2000m,2Ki,不能为1.5, 1500m。 Contaienr resources filed中只配置的Extended Resources必须是Guaranteed QoS。即要么只显示设置了limits(此时requests默认同limits),要么requests和limit显示配置一样。
Scheduler GPU
https://kubernetes.io/docs/tasks/manage-gpus/scheduling-gpus/
这里我们只讨论Kubernetes 1.10中如何调度使用GPU。
在Kubernetes 1.8之前,官方还是建议enable alpha gate feature: Accelerators,通过请求resource alpha.kubernetes.io/nvidia-gpu 来使用gpu,并且要求容器挂载Host上的nvidia lib和driver到容器内。这部分内容,请参考我的博文: 如何在Kubernetes集群中利用GPU进行AI训练 。 从Kubernetes 1.8开始,官方推荐使用Device Plugins方式来使用GPU。 需要在Node上pre-install NVIDIA Driver,并建议通过Daemonset部署NVIDIA Device Plugin,完成后Kubernetes才能发现nvidia.com/gpu。 因为device plugin通过extended resources来expose gpu resource的,所以在container请求gpu资源的时候要注意resource QoS为Guaranteed。 Containers目前仍然不支持共享同一块gpu卡。每个Container可以请求多块gpu卡,但是不支持gpu fraction。
使用官方 nvidia driver 除了以上注意事项之外,还需注意: Node上需要pre-install nvidia docker 2.0 ,并使用nvidia docker替换runC作为docker的默认runtime。 在CentOS上,参考如下方式安装nvidia docker 2.0 : # Add the package repositories distribution=$(. /etc/os-release;echo $ID$VERSION_ID) curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.repo | \ sudo tee /etc/yum.repos.d/nvidia-docker.repo # Install nvidia-docker2 and reload the Docker daemon configuration sudo yum install -y nvidia-docker2 sudo pkill -SIGHUP dockerd # Test nvidia-smi with the latest official CUDA image docker run --runtime=nvidia --rm nvidia/cuda nvidia-smi 以上工作都完成后,Container就可以像请求buit-in resources一样请求gpu资源了: apiVersion: v1 kind: Pod metadata: name: cuda-vector-add spec: restartPolicy: OnFailure containers: - name: cuda-vector-add # https://github.com/kubernetes/kubernetes/blob/v1.7.11/test/images/nvidia-cuda/Dockerfile image: "k8s.gcr.io/cuda-vector-add:v0.1" resources: limits: nvidia.com/gpu: 2 # requesting 2 GPU
使用NodeSelector区分不同型号的GPU服务器
如果你的集群中存在不同型号的GPU服务器,比如nvidia tesla k80, p100, v100等,而且不同的训练任务需要匹配不同的GPU型号,那么先给Node打上对应的Label: # Label your nodes with the accelerator type they have. kubectl label nodes accelerator=nvidia-tesla-k80 kubectl label nodes accelerator=nvidia-tesla-p100
Pod中通过NodeSelector来指定对应的GPU型号: apiVersion: v1 kind: Pod metadata: name: cuda-vector-add spec: restartPolicy: OnFailure containers: - name: cuda-vector-add # https://github.com/kubernetes/kubernetes/blob/v1.7.11/test/images/nvidia-cuda/Dockerfile image: "k8s.gcr.io/cuda-vector-add:v0.1" resources: limits: nvidia.com/gpu: 1 nodeSelector: accelerator: nvidia-tesla-p100 # or nvidia-tesla-k80 etc. 思考:其实仅仅使用NodeSelector是不能很好解决这个问题的,这要求所有的pod都要加上对应的NodeSelector。对于V100这样的昂贵稀有的GPU卡,通常还要求不能让别的训练任务使用,只给某些算法训练使用,这个时候我们可以通过给Node打上对应的Taint,给需要的Pod的打上对应Toleration就能完美满足需求了。
Deploy 建议通过Daemonset来部署Device Plugin,方便实现failover。 Device Plugin Pod必须具有privileged特权才能访问/var/lib/kubelet/device-plugins Device Plugin Pod需将宿主机的hostpath /var/lib/kubelet/device-plugins挂载到容器内相同的目录。
kubernetes 1.8 apiVersion: extensions/v1beta1 kind: DaemonSet metadata: name: nvidia-device-plugin-daemonset spec: template: metadata: labels: name: nvidia-device-plugin-ds spec: containers: - image: nvidia/k8s-device-plugin:1.8 name: nvidia-device-plugin-ctr securityContext: privileged: true volumeMounts: - name: device-plugin mountPath: /var/lib/kubelet/device-plugins volumes: - name: device-plugin hostPath: path: /var/lib/kubelet/device-plugins
kubernetes 1.10 apiVersion: extensions/v1beta1 kind: DaemonSet metadata: name: nvidia-device-plugin-daemonset namespace: kube-system spec: template: metadata: # Mark this pod as a critical add-on; when enabled, the critical add-on scheduler # reserves resources for critical add-on pods so that they can be rescheduled after # a failure. This annotation works in tandem with the toleration below. annotations: scheduler.alpha.kubernetes.io/critical-pod: "" labels: name: nvidia-device-plugin-ds spec: tolerations: # Allow this pod to be rescheduled while the node is in "critical add-ons only" mode. # This, along with the annotation above marks this pod as a critical add-on. - key: CriticalAddonsOnly operator: Exists containers: - image: nvidia/k8s-device-plugin:1.10 name: nvidia-device-plugin-ctr securityContext: privileged: true volumeMounts: - name: device-plugin mountPath: /var/lib/kubelet/device-plugins volumes: - name: device-plugin hostPath: path: /var/lib/kubelet/device-plugins
关于Kubernetes对critical pod的处理,越来越有意思了,找个时间单独写个博客再详细聊这个。
Device Plugins原理图
总结
几个月前,在我的博客 如何在Kubernetes集群中利用GPU进行AI训练 对Kubernetes 1.8如何使用GPU进行了分析,在Kubernetes 1.10中,已经推荐使用Device Plugins来使用GPU了。本文分析了Device Plugin的的原理和工作机制,介绍了Extended Resource,Nvidia Device Plugin的异常处理及改进点,如何使用和调度GPU等。下一篇篇博客,我将对 NVIDIA/k8s-device-plugin 和kubelet device plugin进行源码分析,更加深入了解kubelet和nvidia device plugin的交互细节。
云计算
2018-04-11 00:17:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
概念: 工厂模式负责将大量有共同接口的类实例化。 相关角色: 抽象工厂:创建对象的具体工厂类必须实现这个接口 具体工厂:实现了抽象工厂接口的具体工厂类 抽象产品:创建的具体对象的超类型(父抽象类或父接口) 具体产品:创建的具体对象。
1)简单工厂模式(静态工厂方法模式): 概念:新建一个工厂类,该工厂类有一个静态的工厂方法,根据这个静态工厂方法的入参来决定创建的对象是哪个类型。 实现: // 工厂类 public class Creator { // 静态工厂方法:根据入参condition来决定返回对象的类型。 public static ConcreteProduct factory(condition){ if (condition 满足 条件A) { return new ConcreteProductA(); // 注:ConcreteProduct是ConcreteProductA和ConcreteProductB的超类 } else if (condition 满足 条件B) { return new ConcreteProductB(); } else { // 返回一个默认ConcreteProductX对象 或 直接抛出异常 } } } 缺点:新增类型(eg:ConcreteProductC)时,需要修改静态工厂方法,没有开闭原则的要求。 举例:java.text.DateFormat的getDateInstance()方法。注:DateFormat这个工厂类,是通过重载getDateInstance()方法的方式来进行条件的判断。
2)工厂方法模式: 概念:新建一个工厂接口(或工厂抽象类)、多个具体工厂类,所有的具体工厂类都实现了工厂接口中定义的工厂方法,每个具体工厂类只产生特定类型的对象。 实现: // 抽象工厂 public interface Creator { // 工厂方法 public Product factory(); } // 具体工厂ConcreteCreatorA 实现了工厂接口Creator public class ConcreteCreatorA implements Creator { // 工厂方法 public Product factory() { return new ProductA(); } } // 具体工厂ConcreteCreatorB 实现了工厂接口Creator public class ConcreteCreatorA implements Creator { // 工厂方法 public Product factory() { return new ProductB(); } } 缺点:新增类型(eg:ProductC)时,需要新增对应的具体工厂,当类型较多时(具体工厂类也会随之增多),不容易维护。 注意: 工厂方法模式和模板方法模式的比较: 1)二者都是基于方法的,都是将具体工作交给子类。 2)工厂方法模式将创建的工作推延给子类,模板方法模式将剩余的逻辑交给子类。 举例:java集合中的工厂模式: // 抽象工厂 public interface List extends Collection { Iterator iterator(); // 工厂方法 } // 具体工厂 public class ArrayList implements List { public Iterator iterator() { return new Itr(); // java.util.Vector.Itr 实现了Iterator接口 } } // 具体工厂 public class Vector implements List { public synchronized Iterator iterator() { return new Itr(); // java.util.ArrayList.Itr 实现了Iterator接口 } }
3)抽象工厂模式: 概念:新建一个工厂接口(或工厂抽象类)、一个具体工厂类,这个具体工厂类实现了工厂接口中定义的多个工厂方法,故这个具体工厂类可以产生多种类型的对象。 实现: // 抽象工厂:规定了多个工厂方法 public interface Creator { // 创建A系列产品对象的工厂方法 public ProductA factoryA(); // 创建B系列产品对象的工厂方法 public ProductB factoryB(); } // 具体工厂:实现了抽象工厂的多个工厂方法 public class ConcreteCreator implements Creator { // 创建A类产品对象的工厂方法 public ProductA factoryA() { return new ProductA1(); // 注:ProductA1 implements ProductA } // 创建B类产品对象的工厂方法 public ProductB factoryB() { return new ProductB1(); // 注:ProductB1 implements ProductB } } 缺点:新增其它系列(eg:ProductC)的产品时,需要修改抽象工厂(在抽象工厂中添加新的工厂方法)。
云计算
2018-04-10 20:56:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
(如果看不清楚,保存到本地打开)
以下文字只是时序图的解释说明,重点是看懂时序图。
为了避免主节点/从节点和zk的节点概念看着乱,以下就用Master/Slave称呼了。 ### 1 选Master 系统启动后,所有slave都会尝试在zk树上创建/master节点,创建成功的节点成为Master角色。 然而Master可能会崩溃,所以其他slave要继续监视/master节点。当Master挂掉后,slave重新选举出新的Master(这里的选举就是抢占)。这就要求服务节点挂掉后,/master节点随之删除,以便能让其他slave接到通知,所以master为必须为 临时节点 。
2 Master的准备工作
创建三个父节点/works 、/tasks、 /assign 。(也可以由一个引导程序建) 这三个父节点都是 永久节点 ,且不包含任何数据,作为 目录节点 存在。其子节点分别用于存储可用的slave、任务信息、向从节点分配任务。 创建完成后,Master要监视/works和/tasks两个节点,以便能感知到由哪些slave当前可用,当前有新任务需要分配。
3 Slave的准备工作
slave向zk注册自己的信息,让zk通知Master“我上线了,有新任务分配给我!” 同样,Master也需要及时知道有哪些slave挂了,所以slave在/works目录下创建的必须是 临时节点 。 然后,slave还需要在/assign目录下创建一个节点/work1,并监视这个节点等待任务分配。 create -e /assign/work1 "work1_ip" ls /assign/work1 true
4 客户端提交任务
客户端在/assign下创建一个节点,包含任务信息,即发布一个任务。 并监视该节点,获取任务完成情况 create -s /task/task- "cmd" ls /task/task-000 true
5 Master分配任务
任务发布后,监视了/assign的Master就会收到新任务通知。 然后Master查看/works下的可用slave,从中选一个分配给任务(在slave的目录下新增一个任务节点) create -e /assign/work1/task-000
6 Slave开始干活
slave收到新任务分配通知后,就开始执行任务。 ls /assign/work/task-000 //查看任务信息
然后完成后,添加执行状态,通知客户端。 create /tasks/task-000/status "done" ### 7 客户验收结果
客户端查看任务节点信息,查看任务状态 get /tasks/task-000
以上! 只是描述了用zk实现主从结构的一般原理。 画图挺累的,转载请说明出处。要原图邮箱吧。 ``` 内容整理自《O'REILLY Zookeeper》一书
云计算
2018-04-10 20:49:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
人们在使用及部署Kubernetes时会遇到各种各样的问题。一些挑战是使用Kubernetes时独有的,其他一些挑战则是伴随着一些技术的使用出现的典型问题。
The New Stack发布的《Kubernetes的生态系统状况》报告总结了用户在挑选容器编排解决方案时的不同标准,以及是什么因素限制了用户采用Kubernetes。与安全性或资源优化等标准相比,扩展能力更可能成为编排解决方案的基本需求。其中对Kubernetes最大挑战之一就是,在使用Kubernetes时经常需要改变IT组织中几个部分的角色或职责。
CNCF(云原生计算基金会)在近期的一项调研《Kubernetes部署和安全模式》中问到了人们在使用或部署容器时通常面临的挑战。我们对调研报告中发布的数据进行了独立的分析,将答案范围及关注点限制在那些使用Kubernetes来管理容器的组织上,以此来分析及展示Kubernetes用户所面临的问题。
安全性、存储和网络是最大的挑战
用户对Kubernetes的常见批评之一是它的复杂性。然而此次调研结果表明,企业采用Kubernetes时面临的挑战中,“复杂性”仅排在第五位,用户面临的基础设施相关的挑战更大。在Kubernetes使用者中,安全性的呼声占到了46%,排名紧随其后的是网络和存储,分别是第二、第三位。
只有23%的受访者表示,基于负载来扩展部署对他们而言是一项挑战。这意味着Kubernetes在扩展性方面已成功满足了很大一部分用户的需求。在列表的底部,有10%的人提到了获取供应商支持的问题。很少有人抱怨Kubernetes供应商支持的原因之一,大概是许多部署无须完全依赖于供应商提供的分发版。往后来说,CNCF已推出了Kubernetes Certified Service Provider(Kubernetes认证服务供应商计划)来确保服务供应商达到一定水平的能力,从而让用户能享受到高质量的服务。
超过40%的受访者认为安全性、网络和存储是容器相关的挑战
更大规模的组织存在更多的挑战
与其他研究相似,我们发现更大规模的组织更可能列出更多它们关心的挑战。例如,在拥有员工人数在1000人及以上的组织中,有55%的组织表示安全性是一项挑战,而在员工人数少于100人的组织中只有39%持有相同的想法。在这种情况下,与可靠性等其他类型一样,大型企业的需求可能和小型企业不同。在其他领域,例如网络,与仅和使用的容器数量相比,IT基础架构的规模和广度(带宽和站点数量)可能会给Kubernetes带来更多独有的挑战。事实上,在拥有6个或6个以上集群的组织中,引发网络挑战的比例从42%上升到了53%。
也有一些挑战不符合上述模式。对于存储,一个解释是技术“问题”并不在于可扩展性。在监控方面,中型组织更可能会面临挑战。小型组织通常不需要创建正式的监控流程,较大规模的组织有资源创建更强大的定制监控系统。而在中间地带徘徊的是那些员工人数在100到999人的组织。
在拥有1000人及以上组织中,安全性和网络更可能被视为与容器相关的挑战
本地部署vs.云上部署带来的不同挑战
影响组织和容器相关挑战的另一个因素是,他们是将容器部署在公共云上还是本地服务器上。
对那些将全部容器都部署在本地服务器上的组织而言,存储是最常见的挑战。这可能是因为这些组织要管理自己的存储基础架构,甚至可能需要独立IT团队来处理这一问题。
在仅使用本地服务器的使用者中,54%的用户面临存储方面的挑战,而拥有公有云的组织仅有34%
对仅在公有云上使用容器的组织而言,监控和日志对他们来说是一大挑战。虽然云供应商是可以实现可扩展性的,但那些只使用本地服务器的组织也很少说到扩展部署是一项挑战。
CNCF的此次调研还调查了用户常用的几种类型的云原生基础设施和工具,其中一些是专门用于和Kubernetes配合使用的。后续文章中我们将讨论Kubernetes用户解决他们面临的挑战时最常用的工具,欢迎保持关注。
云计算
2018-04-04 13:25:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
摘要: 对于创业成长型的企业来说,离线计算已经必不可少了,通过离线计算我们可以生成复杂的业务报表,通过离线计算我们也能精确的算出用户画像。离线计算已经当今的企业中成为了不可或缺的存在。那么使用弹性计算能够对离线计算领域带来什么好处呢?本文将介绍如何使用弹性计算节省企业离线计算成本。
背景
对于目前创业成长型的企业来说,离线计算已经必不可少了,通过离线计算我们可以生成复杂的业务报表,通过离线计算我们也能精确的算出用户画像。离线计算已经当今的企业中成为了不可或缺的存在。那么使用弹性计算能够对离线计算领域带来什么好处呢?小编告诉你好处肯定是有的,且听小编娓娓道来。
常见离线计算架构
从上图中我们用最常见的离线计算开源产品Hadoop作为例子,如上图所示大家会把存储服务跟计算符合放在一台ECS上。但是随着业务的扩张,我们对计算的需求越来越大,我们需要用数据做各种运算,为了增强离线集群的计算能力,我们会想到直接通过添加计算节点的方式来获得。但是随着计算节点的添加我们会遇到另一个问题,那就是资源利用率不够的问题。虽然计算通过扩容能够提升计算能力,但是数据可不会像计算那样突然猛增,所以对于存储来说扩容操作会暂时导致资源利用率的下降。再加上离线计算每天的计算时间只有一段时间,扩容之后的ECS在每天离线计算完成之后将大量空闲,这段时间将是对成本极大的浪费!那么很多客户会想是否有一种方法能够将计算存储分开进行扩容,并且在离线计算的时候对计算节点扩容,在计算完成之后将扩容的计算节点释放掉,从而达到节省成本的目的。这个架构肯定是有的,不过为了让大家更好的理解接下来的架构,小编先要给大家介绍一下竞价实例和弹性伸缩。
竞价实例简单介绍
从上面的图中大家可以发现竞价实例是一种按照供需关系变化价格波动的一种后付费类型实例,相对于按量付费实例价格有较低的折扣。不过同时竞价实例是有可能随时被阿里云释放的,这点请大家务必注意。简而言之就是竞价实例便宜,但是可能随时被释放。
弹性伸缩简单介绍
弹性伸缩总共有如下三个优点:
提升容错能力
弹性伸缩会定时检查ECS健康状态,如果发现ECS不健康,那么ECS将会创建一台新的ECS去替换它并且将不健康的ECS释放掉。
增强可用性
弹性伸缩能够通过定时、自动伸缩保证应用程序始终有合适的容量去满足当前的请求流量。
优化成本
弹性计算通过自动动态的方式按需增减实例,在需要的时候添加实例,不需要的时候释放实例,从而节约IT成本。
存储计算分离之后的离线计算架构
经过上面对竞价实例以及弹性伸缩简单的介绍,现在小编可以大声的告诉大家,存在一种基于弹性计算的架构能够不仅能够满足用户对于海量数据的计算能力需求,而且还能降低大家的计算成本。
将Hadoop的存储计算节点分离 使用弹性伸缩来按时、按需创建、释放计算计算节点 计算节点的付费类型采用竞价方式
这里相信大家肯定又有疑问了,竞价实例不是会被阿里云释放吗,这里使用竞价实例合适吗?答案是绝对合适的。第一竞价实例的价格变量付费便宜很多,第二就算竞价实例被阿里云释放了,影响的也紧紧只是我们离线计算的速度。在上面的架构图上小编也留了一台ECS没有加入伸缩组,这一台ECS我们可以采用预付费的方式为我们计算资源提供“保底”的计算能力,当然我们也可以根据自身的需要来设置“保底”ECS的数量。
使用竞价实例+弹性伸缩搭建离线计算架构Tips
Spot Instance出价策略
通过阿里云ECS的售卖页面查看竞价实例多可用区的历史价格,从而选择一个合理的出价。
AutoScaling伸缩组配置 不要在整点触发定时扩容任务,整点扩容的客户比较多,大家可以选择整点过后5-10分钟进行扩容,这样价格相对会低一些 通过监测计算节点的CPU/MEM指标来触发报警任务 通过在伸缩组选择多可用区来增加成功购买竞价实例的概率 在伸缩组内配置最大ECS数目防止弹性伸缩创建ECS数量超过预期 创建一条伸缩任务进行手动扩容以备不时之需
使用Spot Instance + AutoScaling成本

原文链接
阅读更多干货好文,请关注扫描以下二维码:
云计算
2018-04-04 10:50:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
摘要: 对于创业成长型的企业来说,离线计算已经必不可少了,通过离线计算我们可以生成复杂的业务报表,通过离线计算我们也能精确的算出用户画像。离线计算已经当今的企业中成为了不可或缺的存在。那么使用弹性计算能够对离线计算领域带来什么好处呢?本文将介绍如何使用弹性计算节省企业离线计算成本。
背景
对于目前创业成长型的企业来说,离线计算已经必不可少了,通过离线计算我们可以生成复杂的业务报表,通过离线计算我们也能精确的算出用户画像。离线计算已经当今的企业中成为了不可或缺的存在。那么使用弹性计算能够对离线计算领域带来什么好处呢?小编告诉你好处肯定是有的,且听小编娓娓道来。
常见离线计算架构
从上图中我们用最常见的离线计算开源产品Hadoop作为例子,如上图所示大家会把存储服务跟计算符合放在一台ECS上。但是随着业务的扩张,我们对计算的需求越来越大,我们需要用数据做各种运算,为了增强离线集群的计算能力,我们会想到直接通过添加计算节点的方式来获得。但是随着计算节点的添加我们会遇到另一个问题,那就是资源利用率不够的问题。虽然计算通过扩容能够提升计算能力,但是数据可不会像计算那样突然猛增,所以对于存储来说扩容操作会暂时导致资源利用率的下降。再加上离线计算每天的计算时间只有一段时间,扩容之后的ECS在每天离线计算完成之后将大量空闲,这段时间将是对成本极大的浪费!那么很多客户会想是否有一种方法能够将计算存储分开进行扩容,并且在离线计算的时候对计算节点扩容,在计算完成之后将扩容的计算节点释放掉,从而达到节省成本的目的。这个架构肯定是有的,不过为了让大家更好的理解接下来的架构,小编先要给大家介绍一下竞价实例和弹性伸缩。
竞价实例简单介绍
从上面的图中大家可以发现竞价实例是一种按照供需关系变化价格波动的一种后付费类型实例,相对于按量付费实例价格有较低的折扣。不过同时竞价实例是有可能随时被阿里云释放的,这点请大家务必注意。简而言之就是竞价实例便宜,但是可能随时被释放。
弹性伸缩简单介绍
弹性伸缩总共有如下三个优点:
提升容错能力
弹性伸缩会定时检查ECS健康状态,如果发现ECS不健康,那么ECS将会创建一台新的ECS去替换它并且将不健康的ECS释放掉。
增强可用性
弹性伸缩能够通过定时、自动伸缩保证应用程序始终有合适的容量去满足当前的请求流量。
优化成本
弹性计算通过自动动态的方式按需增减实例,在需要的时候添加实例,不需要的时候释放实例,从而节约IT成本。
存储计算分离之后的离线计算架构
经过上面对竞价实例以及弹性伸缩简单的介绍,现在小编可以大声的告诉大家,存在一种基于弹性计算的架构能够不仅能够满足用户对于海量数据的计算能力需求,而且还能降低大家的计算成本。
将Hadoop的存储计算节点分离 使用弹性伸缩来按时、按需创建、释放计算计算节点 计算节点的付费类型采用竞价方式
这里相信大家肯定又有疑问了,竞价实例不是会被阿里云释放吗,这里使用竞价实例合适吗?答案是绝对合适的。第一竞价实例的价格变量付费便宜很多,第二就算竞价实例被阿里云释放了,影响的也紧紧只是我们离线计算的速度。在上面的架构图上小编也留了一台ECS没有加入伸缩组,这一台ECS我们可以采用预付费的方式为我们计算资源提供“保底”的计算能力,当然我们也可以根据自身的需要来设置“保底”ECS的数量。
使用竞价实例+弹性伸缩搭建离线计算架构Tips
Spot Instance出价策略
通过阿里云ECS的售卖页面查看竞价实例多可用区的历史价格,从而选择一个合理的出价。
AutoScaling伸缩组配置 不要在整点触发定时扩容任务,整点扩容的客户比较多,大家可以选择整点过后5-10分钟进行扩容,这样价格相对会低一些 通过监测计算节点的CPU/MEM指标来触发报警任务 通过在伸缩组选择多可用区来增加成功购买竞价实例的概率 在伸缩组内配置最大ECS数目防止弹性伸缩创建ECS数量超过预期 创建一条伸缩任务进行手动扩容以备不时之需
使用Spot Instance + AutoScaling成本

原文链接
云计算
2018-04-04 10:45:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Openstack是把大机器虚拟成很多小机器从而达到大的分成小的来用,大大提高资源使用率。
Hadoop是把小机器合起来用,用于解决单台大机器无法解决的计算和存储等问题。
云计算
2018-04-04 06:00:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Volume 提供了非常好的数据持久化方案,不过在可管理性上还有不足。
拿前面 AWS EBS 的例子来说,要使用 Volume,Pod 必须事先知道如下信息: 当前 Volume 来自 AWS EBS。 EBS Volume 已经提前创建,并且知道确切的 volume-id。
Pod 通常是由应用的开发人员维护,而 Volume 则通常是由存储系统的管理员维护。开发人员要获得上面的信息: 要么询问管理员。 要么自己就是管理员。
这样就带来一个管理上的问题:应用开发人员和系统管理员的职责耦合在一起了。如果系统规模较小或者对于开发环境这样的情况还可以接受。但当集群规模变大,特别是对于生成环境,考虑到效率和安全性,这就成了必须要解决的问题。
Kubernetes 给出的解决方案是 PersistentVolume 和 PersistentVolumeClaim。
PersistentVolume (PV) 是外部存储系统中的一块存储空间,由管理员创建和维护。与 Volume 一样,PV 具有持久性,生命周期独立于 Pod。
PersistentVolumeClaim (PVC) 是对 PV 的申请 (Claim)。PVC 通常由普通用户创建和维护。需要为 Pod 分配存储资源时,用户可以创建一个 PVC,指明存储资源的容量大小和访问模式(比如只读)等信息,Kubernetes 会查找并提供满足条件的 PV。
有了 PersistentVolumeClaim,用户只需要告诉 Kubernetes 需要什么样的存储资源,而不必关心真正的空间从哪里分配,如何访问等底层细节信息。这些 Storage Provider 的底层信息交给管理员来处理,只有管理员才应该关心创建 PersistentVolume 的细节信息。
Kubernetes 支持多种类型的 PersistentVolume,比如 AWS EBS、Ceph、NFS 等,完整列表请参考 https://kubernetes.io/docs/concepts/storage/persistent-volumes/#types-of-persistent-volumes
下节我们用 NFS 来体会 PersistentVolume 的使用方法。
书籍:
1.《每天5分钟玩转Kubernetes》
https://item.jd.com/26225745440.html
2.《每天5分钟玩转Docker容器技术》
https://item.jd.com/16936307278.html
3.《每天5分钟玩转OpenStack》
https://item.jd.com/12086376.html
云计算
2018-04-04 05:37:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
数据结构
pg_interval_t{
vector up, acting;//当前pg_interval的up和acting的osd列表
epoch_t first, last;//该interval的起始和结束epoch
bool maybe_went_rw;//在这个阶段是否可能有数据读写
int32_t primary;//主osd
int32_t up_primary;//up状态的主osd
}
PriorSet {
Const bool ec_pool;//是否是ec pool
set probe; //需要probe的osd
set down; //当前是down的osd
map blocked_by; //导致pg_down为true的osd及对应osdmap的epoch
bool pg_down; //pg是否为down
boost::scoped_ptr pcontdec;//判断pg是否可恢复的函数指针
}
pg_info_t {
spg_t pgid;//pgid和shardid信息
eversion_t last_update; // last object version applied to store.当前osd最新的一次更新
eversion_t last_complete; // last version pg was complete through.要保证所有osd都更新的版本号
epoch_t last_epoch_started;// last epoch at which this pg started on this osd //最新的一次变成active后的epoch
version_t last_user_version; // last user object version applied to store
eversion_t log_tail; // oldest log entry.
hobject_t last_backfill; //backfill的object指针,正常情况为hobject_t::get_max()
interval_set purged_snaps;
pg_stat_t stats;//统计信息
pg_history_t history;//历史版本
pg_hit_set_history_t hit_set;//cache tier相关
};
pg_log_t{
eversion_t head; // 最新的pg_log_entry版本
eversion_t tail; // 最老的pg_log_entry
eversion_t can_rollback_to;//可以回滚的版本
eversion_t rollback_info_trimmed_to;//回滚场景可以trim的版本
list log; //具体的pg_log_entry信息
};
pg_missing_t {
map missing;//丢失对象和版本
map rmissing;
};
class MissingLoc {
map needs_recovery_map;//需要恢复的对象和版本信息
map > missing_loc;//表示该对象在哪些osd上存在
set missing_loc_sources;//存在missing对象的osd列表
PG *pg;
set empty_set;
};
概要分析
Pg状态机的总体状态转换图
Peering过程中的交互图
整体来看peering分为4个阶段:Getinfo,Getlog,Getmissing,Active。必要的时候在getmissing之后会有个waitupThru。
Getinfo:pg的主osd收集其他从osd上pg_info_t的信息。
GetLog:选出拥有权威日志的osd,如果不是主则从该osd拉去权威日志给主osd。
GetMissing:主osd去其他osd上拉取pg_entry_t,通过和权威日志对比pg_entry_t来判断各个osd上缺失的object信息。
Active:激活主osd和从osd
基本概念
临时PG、acting set和up set
acting set为pg对应的osd列表,其中列表第一个为主osd。一般情况下acting set和up set是一样的。假设一个pg的acting为[1,2,3]。当1挂了后up为[4,2,3]。这个时候由于4是新加入该pg的osd,上面并没有数据,且需要进行backfill。这个时候会产生一个临时pg。则up为[4,2,3]。但是acting还是为[2,3]。
Up_thru
简单举例:当某个pg对应的osd列表为[1,2],min_size为1。当osd.1和osd.2依次挂掉,可能会有2种情况:
情况1:osd.1挂了,osd.2还未完成peering阶段,osd.2紧接着挂了。这个时候数据无法写入osd.2。
情况2:osd.1挂了,osd.2完成peering进入active后挂了。这个时候该pg存在一个时间窗口可以正常写入数据。
当osd.1重新启动后,如果是情况1,因为没有新数据写入,则pg可以正常完成peering。如果是情况2,有可能一部分数据只在osd.2上存在,则无法完成peering。
为了区分情况1和情况2,所以引入up_thru。up_thru记录了每个osd完成peering的epoch值,osdmap会维护up_thru[osd]的数组。
引入up_thru后,假设初始的up_thru为0。则情况1中up_thru[osd.2]为0,而情况2中up_thru[osd.2]不为0。
Osdmap维护osd_info信息,其中包含up_from和up_thru。up_from到up_thru之间的epoch是无法写入数据的。
past_interval
表示一个epoch的连续序列,在这个epoch的连续序列中该pg的acting和up都没有发生变化。例如:
epoch 1 2 3 4 5 6 7 8 9
失效 osd Osd.3 Osd.4 Osd.5 Osd.2 Osd.10 Osd.11 Osd.12 Osd.13
up [0,1,2] [0,1,2] [0,1,2] [0,1,2] [0,1,6] [0,1,6] [0,1,6] [0,1,6] [0,1,6]
acting [0,1,2] [0,1,2] [0,1,2] [0,1,2] [0,1,6] [0,1,6] [0,1,6] [0,1,6] [0,1,6]

Past_interval
Current_interval

Statechart状态机
状态:继承boost::statechart::state。状态分为2种:
第一种是没有子状态的状态。例如:
struct Reset : boost::statechart::state< Reset, RecoveryMachine >, NamedState
进入该状态则直接调用构造函数。
第二种是有子状态的状态。例如:
struct Started : boost::statechart::state< Started, RecoveryMachine, Start >, NamedState
struct Start : boost::statechart::state< Start, Started >, NamedState
表示Start是Started的第一个子状态。如果进入Started状态会进入Start

事件:继承boost::statechart::event。例如:
struct MakePrimary : boost::statechart::event< MakePrimary >

状态响应事件:
大致分为3种:
boost::statechart::transition< NeedActingChange, WaitActingChange >
表示收到NeedActingChange事件,进入WaitActingChange 状态,一般投递事件通过post_event方法。
boost::statechart::custom_reaction< ActMap >
这种一般会有 boost::statechart::result react(const ActMap&)方法对应处理事件
boost::statechart::custom_reaction< NullEvt >
NullEvt没有对应的react方法处理,什么也不做,一般只是为了流程上的调度使用。
主要流程
Started/Primary/Active到peering状态转换
当有osd故障或者启动,会导致osdmap的变化。对应pg的主osd会收到mon发送的osdmap信息。当投递advmap事件后,进入reset状态。

在reset状态,投递Actmap事件进入Started状态,则直接进入Started的第一个子状态Start。构造函数中判断当前osd是osd的主pg,则投递MakePrimary事件,进入Primary状态。于是进入GetInfo状态
GetInfo流程
GetInfo的核心功能都在构造函数中完成。主要包含3个函数:
generate_past_intervals:计算past_intervals的值。
1)调用_calc_past_interval_range来推测所有past_intervals的epoch的start和end范围。
start值:上一次pg clean状态的epoch,pg创建的epoch,最老的osdmap的epoch中较大的值。
end值:一般为当前osdmap的epoch。
2)根据start,end值来遍历历史的osdmap。通过依次比较相邻的osdmap来获取past_intervals值。这里要注意对于maybe_went_rw的判断。
build_prior:根据当前acting,up以及past_intervals构建osd列表
1)把当前acting和up的osd添加到probe的osd列表中。
2)倒序遍历past_intervals中的每一个pg_interval_t,如果interval.last小于info.history.last_epoch_started,表示这个interval在上一次peering之前的,直接break。如果acting为空,或者该interval无数据写入,则continue。遍历interval的acting列表中的osd,如果osd在当前osdmap中是up则加入到probe和up_now中,否则加入到down列表中。通过pcontdec判断当前pg是否处于down状态,pcontdec在多副本场景只要有一个为up_now存在,则为true,ec场景要大于等于M个up_now存在才为true。
3)判断是否要进行up_thru操作。如果该osd在当前osdmap的up thru小于最新的osdmap的epoch,则需要更新。
4)发送pg_query_t给所有prior_set.probe的osd,并将发送osd的信息保存在GetInfo.peer_info_requested和pg->blocked_by中。
5)Pg主osd收到其他osd返回的信息,调用handle_pg_notify。投递MNotifyRec事件,则调用boost::statechart::result PG::RecoveryState::GetInfo::react(const MNotifyRec& infoevt)。
a.从peer_info_requested和pg->blocked_by中移除对应的osd
b.调用proc_replica_info来处理各个副本的pg_info_t信息。
1.检查peer_info中是已经有pg_info_t的信息(peer_info信息在Reset状态的时候会被clear),如果有了直接返回false,否则保存到peer_info中
2.使pg不在osd的scrub的操作队列里。
3.通过merge从osd pg的history到本pg info.history。更新的方法就是使用最大的epoch。如果有更新则dirty_info为true。
4.如果from的osd不在当前的up和acting列表中,则放到stray_set中,当pg clean后调用purge_strays清理数据。
5.更新PGheartbeat_peers,如果需要更新,OSD.heartbeat_need_update设置为true。
c.如果新的pg.info.history.last_epoch_started更大,说明从osd上有更新的上一次peering版本,这个时候需要重新构建prior_set,目的是将不许要probe的osd从peer_info_requested中去掉,以便更快触发GotInfo事件。
d.当peer_info_requested为空,即所有pg_info_t都保存在peer_info中了。倒序遍历past_intervals,以及具体intervals中的acting列表的osd,至少要有一个osd是up状态并且非incomplete状态,不然pg设置为down状态。
e.投递GotInfo事件,进入GetLog状态
GetLog状态流程
GetLog的核心功能也在构造函数中完成。
1)调用choose_acting来选择一个权威日志的osd。整个choose_acting要做的事情包括:
1.选出一个权威日志的osd
2.填充pg的字段:actingbackfill,want_acting,backfill_targets
a.调用find_best_info函数从peer_info中选出一个权威日志的osd。
1.计算所有peer_info中的最大的last_epoch_start,以及在最大last_epoch_start情况下最小的last_update,为min_last_update_acceptable。
2.过滤掉last_update比min_last_update_acceptable小的,last_epoch_started比max_last_epoch_started_found小的,pg是incomplete的情况。
3.如果pg是ec pool的pg则选择last_update较小的,如果是多副本则选择last_update较大的。尽量选log_tail较小的,也就是日志较多的,最后尽可能选主。
综上所述。权威日志的osd要满足的条件:
last_epoch_start必须是最大的,必须是非incomplete的osd,last_update要大于等于min_last_update_acceptable。然后是日志最多的,或者是主。
b.如果find_best_info没有选举出权威日志的osd,且up不等于acting,则把pg加到pg_temp_wanted中,申请临时PG。最后choose_acting返回false
c.如果权威日志的osd是incomplete,则需要从peer_info中complete的osd列表中再选一个权威日志的osd。
d.如果所有的peer_info的osd都支持EC特性,则compat_mode为true。
e.如果是多副本pool,调用calc_replicated_acting。如果是ec pool,则调用calc_ec_acting。
calc_replicated_acting:
这个函数的目的就是要找到主osd以及获取backfill和acting_backfill列表。
1.选pg的主osd,即primary变量。如果up_primary(当前主)的pg_info_t信息的last_update大于权威日志的log_tail,即和权威日志有重叠,则primary就是up_primary。否则权威日志的osd就是主osd。把主osd设置为want_primary,want,acting_backfill中添加主osd。
2.遍历当前up列表的pg_info_t信息,如果last_update小于MIN(primary->second.log_tail,auth_log_shard->second.log_tail)或者是incomplete状态,表示需要backfill的osd,加入到backfill和acting_backfill中。否则是正常的或者recovery的osd放acting_backfill中。
3.遍历acting中存在但是up中不存在的osd。如果存在这样的osd,且和权威日志有重叠,则加到acting_backfill中。
4.遍历all_info中存在但是up和acting中不存在的osd。如果存在这样的osd,且pg_info信息和权威日志有重叠,则加到acting_backfill中。
calc_ec_acting:
1.初始话vector want大小为ec pool size,以及初始值为CRUSH_ITEM_NONE
2.遍历want,其实也是遍历up。将up中pg_info是非incomplete和last_update大于权威日志的tail,则将up中的osd加入到want中。
3.up中其他的osd加入到backfill列表中
4.want中的第一个osd为want_primary。
f.如果want的size小于pool的min_size,则choose_acting返回失败。
g.根据want构造set have。调用recoverable_predicate判断是否可以恢复
h.如果want不等于acting,但是want等于up。则发送MOSDPGTemp消息给mon,申请临时pg,返回失败。
2)如果权威日志的osd就是自己,直接投递GotLog事件,进入GetMissing状态
3)如果权威日志的osd不是自己,更新request_log_from为peer_info中最小的且大于权威日志的tail的last_update。去拉去权威日志
4)主osd收到权威日志的msg,投递GotLog事件,调用boost::statechart::result PG::RecoveryState::GetLog::react(const GotLog&)
5)GotLog事件处理,如果有msg,则调用proc_master_log来合并权威日志。完成后进入GetMissing状态
a.调用PGLog::merge_log来合并权威日志。输入包括权威日志的信息oinfo和olog和本pg的info信息
权威日志 本地日志
From (20,2)
(20,3)
To (20,4) (20,4)
(20,5) (20,5)
(20,6) (20,6)

1.如果权威日志的tail小于本地log的tail,说明本地日志有缺失,计算权威日志和本地日志重叠的版本号to。从权威日志中拷贝本地日志缺失的log_entry_t到本地,更新本地info的log_tail为权威日志的log_tail。
分歧日志
权威日志
本地日志
(20,2)obj1
(20,2)obj1
(20,3)obj2
(20,3)obj2
(20,4)obj3
(20,4)obj3
(20,5)obj2
(20,6)obj2

2.如果权威日志的head小于本地日志的head,说明最新的数据在本地日志中存在。调用
rewind_divergent_log来处理分歧日志。
a.先根据权威日志的head来获取本地分歧日志的列表,将本地日志的info和pglog的head信息以及last_update信息都更新为权威日志的head。说明分歧日志是需要被trim的
b.将分歧日志按照object分类,将同一个object的pg_log_entry_t放到一个list中。
c.调用_merge_object_divergent_entries来处理对象的分歧日志集合。将所有分歧日志给加入trim的事务中。1)如果本地日志该对象最新的版本大于或等于分歧日志的第一个版本,说明该对象后续还有对于该object的操作完成。把这个对象删除,missing列表中have设置为空,通过missing列表来恢复。这个场景有一些没明白。2)如果分歧日志的前一个版本是eversion_t或者第一条日志是clone日志,说明是个创建日志,没必要恢复,直接从missing列表中删除,这个对象也删除。3)如果这个对象已经在missing列表中了,且分歧日志的前一个版本已经是当前item的have,说明当前版本就是最新的版本,则从missing列表中移除。否则item的need就是分歧日志的前一个版本。如果分歧日志的前一个版本比日志的最老的版本还要老,则另外处理。4)5)先判断分歧日志是否有一条日志可以rollback。如果可以rollback则放到rollback的队列中,否则将对象删除,通过missing列表恢复。
权威日志 本地日志
(20,2)obj1 (20,2)obj1
(20,3)obj2 (20,3)obj2
(20,4)obj3 (20,4)obj3
from (20,5)obj2
(20,6)obj2

3.如果权威日志的head大于本地日志head,说明本地日志的数据不够新。先通过后续遍历权威日志找到from点。把从from开始的权威日志加到本地pg_log中。同时将pg_log_entry_t的object加入到missing列表中。
权威日志 本地日志
(20,2)obj1 (20,2)obj1
(20,3)obj2 (20,3)obj2
(20,4)obj3 (20,4)obj3
from (22,5)obj2 (20,5)obj2
(22,6)obj2 (20,6)obj2
入上图,权威日志的head大于本地日志head有可能的一种特殊情况。将本地日志(20,5)(20,6)放到分歧日志列表中,并从本地日志中删除这些分歧日志,更新为权威日志的pg_log_entry。再根据2中处理分歧日志的5条规则来处理这些分歧日志。
b.更新权威日志osd的peer_info和peer_missing到本地pg中。Merge权威日志的history信息到本地的history信息中。整个proc_master_log完成后,进入GetMissing阶段。
GetMissing流程
1)构造函数中遍历actingbackfill的副本osd的pg_info_t信息。其中需要backfill和没有missing对象的osd直接continue。剩下的都是需要recovery的副本osd。如果副本osd的pg_info信息的last_epoch_started(上一次达到active的版本号)大于log_tail,说明只有部分日志的对象需要回复,所以只需要拉去从last_epoch_start到log_head的日志。否则就是全部日志都需要恢复,则拉去全部日志。同时把需要拉去的osd加入到peer_missing_requested中。
2)如果peer_missing_requested为空,表示没有需要拉去日志的osd。则直接进入到Active状态。
3)如果peer_missing_requested不为空,则等待副本osd返回pg_log的信息。当副本信息返回的时候会投递MLogRec事件,调用boost::statechart::result PG::RecoveryState::GetMissing::react(const MLogRec& logevt)。其中调用proc_replica_log来处理返回的副本pg_log。并且peer_missing_requested删除对应的osd。当peer_missing_requested为空,说明所有副本osd的pg_log信息都返回了,则进入到Active状态。重点看proc_replica_log如何处理副本osd的pg_log信息
副本日志 权威日志
(20,2)obj1 (20,2)obj1
(20,3)obj2 (20,3)obj2
lower_bound (20,4)obj3 (20,4)obj3
Fromiter
分歧日志 (20,5)obj2
分歧日志 (20,6)obj2

副本日志 权威日志
(20,2)obj1 (20,2)obj1
(20,3)obj2 (20,3)obj2
lower_bound (20,4)obj3 (20,4)obj3
Fromiter
(20,5)obj2
(20,6)obj2

a.先通过对比副本日志和权威日志的信息,找到lower_bound和fromiter。可能出现的场景为上面2种。如果有分歧日志,则处理分歧日志。
b.值得注意的是本以为副本osd的Missing信息应该是在proc_replica_log中来获得,但是实际上并没有。
Active流程
主要流程在PG::activate中完成。
1)更新pg_info的信息,主要是last_epoch_started字段为当前的epoch。last_update_ondisk为last_update,如果pg的missing为空则last_complete为last_update
2)如果是主osd还需要激活从osd。1.如果从osd的last_update等于主osd的last_update。说明这个从osd是clean的,加入到activator_map中,后续在do_infos中发送pg_info来激活。2.需要backfill的osd发送最新的pg_info和pg_log_entry,peer_missing清空。3.需要recovery的osd,发送最新的pg_info和丢失的pg_log_entry,更新peer_missing。
3)设置missing_loc,调用add_active_missing将missing和peer_missing的oid加入到needs_recovery_map中。needs_recovery_map只保存了missing的object和pg_missing_t::item的对应关系。最终我们还需要missing的object和osd之间的关系。遍历所有osd,调用add_source_info,来设置missing_loc。
4)当收到副本osd的回复后,触发MInfoRec事件,调用boost::statechart::result PG::RecoveryState::Active::react(const MInfoRec& infoevt)。每回来一个osd的信息peer_activated插入osd信息,当peer_activated等于actingbackfill,调用all_activated_and_committed,表示收到所有osd的返回信息。投递AllReplicasActivated事件。
5)boost::statechart::result PG::RecoveryState::Active::react(const AllReplicasActivated &evt)处理所有副本返回的事件。清理和设置pg的状态。设置history.last_epoch_started等于pg的last_epoch_started。调用check_local来检查已经被删除的对象是否还在。调用ReplicatedPG::on_activate来触发recovery或者backfill流程。
云计算
2018-04-03 17:25:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
摘要: 全球开源区块链领域影响最为广泛的Hyperledger Fabric日前宣布了1.1版本的正式发布,带来了一系列丰富的新功能以及在安全性、性能与扩展性等方面的显著提升。阿里云容器服务区块链解决方案第一时间同步升级,在v1.1新功能的基础上,提供了弹性裸金属服务器(神龙)、内置容器化Explorer、集成阿里云日志服务等方面的增强。

作为全球开源区块链领域影响最为广泛的项目之一,超级账本近日宣布了Hyperledger Fabric v1.1的正式发布。此次升级带来了一系列丰富的新功能以及在安全性、性能与扩展性等诸多方面的显著提升。更多详情可参考下述资料: Hyperledger Fabric v1.1官方发布博客: https://www.hyperledger.org/blog/2018/03/20/hyperledger-fabric-v1-1-released Hyperledger Fabric GitHub源代码项目: https://github.com/hyperledger/fabric Hyperledger Fabric项目路线图: https://wiki.hyperledger.org/projects/fabric/roadmap

与此同时,作为对开源区块链生态的支持,阿里云容器服务区块链解决方案也进行了同步升级。这次升级不仅第一时间在Kubernetes集群上支持Hyperledger Fabric v1.1.0 GA版,并且在以下几个方面带来了新的功能增强和使用体验的优化: 支持新的Node.js类型chaincode和相关示例,以及在国内云环境落地的适配 支持chaincode级别的账本数据加密新功能和相关示例 支持新的connection profile和自动生成功能 以及其他Hyperledger Fabric v1.1新功能的支持 支持阿里云弹性裸金属服务器(神龙) 内置容器化的Hyperledger Blockchain Explorer 集成阿里云日志服务 优化区块链网络删除过程的数据目录清理

本文将对这些主要新功能进行技术方面的解读。在开始之前,我们准备了一段小视频,可以让大家快速了解在阿里云容器服务Kubernetes环境中使用该区块链解决方案的大致流程。
新功能:Node.js类型Chaincode支持

在此次Hyperledger Fabric v1.1的升级中,在原有的Golang类型之上(Java类型尚未正式宣布支持),加入了对流行的Node.js编程语言类型的智能合约(即chaincode)的支持。这将进一步扩大和丰富Hyperledger Fabric的应用开发生态,吸引更多的开发者加入到区块链应用的创新中来。

在此基础上,通过对Fabric的源代码分析,我们在区块链解决方案中解决了在国内网络环境中可能遇到的Node.js类型chaincode实例化(instantiation)过程超时失败、chaincode镜像无法创建的局限性。

如需体验Node.js类型的chaincode,可采用CLI方式或者Client SDK方式,通过参数指定chaincode语言类型即可。解决方案中提供了基于官方cli-test.sh的示例脚本、以及基于官方的balance-transfer的client SDK示例程序。

CLI方式的Node.js类型chaincode示例

Node.js类型示例chaincode源代码位置:
SDK方式的Node.js类型chaincode示例



需要说明的是,由于Node.js应用自身编译构建的特点,Hyperledger Fabric中Node.js类型的chaincode实例化(instantiation)时间相比Golang类型的要更长,所以在这个环节需要稍作等待。

新功能:Chaincode级别对账本数据的加密和签名

Hyperledger Fabric v1.1新增支持在chaincode级别对交易的数据(即StateDB里Key-Value中的Value,通过调用chaincode时传入)进行加密/解密、签名/验证。这样能实现业务应用将商业敏感数据交给区块链之后,数据全程(包括落地到账本)在原有的安全机制之上进一步添加了标准的或用户可扩展的加密算法和签名算法的保护,进一步提高了数据的安全性。

关于此功能的的官方文档介绍可参考:
http://hyperledger-fabric.readthedocs.io/en/latest/chaincode4ade.html?highlight=chaincode%20encryption#chaincode-encryption

在容器服务的区块链解决方案中,我们提供了官方的EncCC示例程序用于在CLI环境中运行,方便用户了解此功能的使用和工作机制。在该示例中,首先对键"key1"的值“value1”进行加密并记录到账本上;然后读取键"key1"并解密其值,输出显示"value1";接着,再对键“key2”的值"value2"进行加密以及数字签名并记录到账本上,最后读取键“key2”并解密、验证签名得到最终值,输出显示"value2"。

需要说明的是,使用该CLI示例程序之前,请先创建对应的channel(例如通过运行cli-tset.sh)。

示例程序源代码参考:
https://github.com/hyperledger/fabric/tree/release-1.1/examples/chaincode/go/enccc_example
新功能:Connection Profile用于Client SDK应用程序

针对不同类型的区块链应用程序有着各不相同的对区块链网络拓扑和连接信息的描述方式的问题,为了提供更为统一规范的应用连接配置,Hyperledger Fabric v1.1开始支持Connection Profile, 目前主要面向Node.js类型的Client SDK应用程序,未来有望推广到更多类型的SDK应用上。

在此次区块链方案升级中,也同步支持此功能,并且能够根据用户输入的区块链网络拓扑配置(channel, peer, orderer, ca等)、公网访问地址和端口等信息,自动生成Connection Profile应用连接配置文件(即network-config.yaml)并提供给用户下载。而在我们的Client SDK示例 balancer-transfer-app 中也通过一个 脚本 实现一键下载所有证书密钥以及Connection Profile等配置文件。
如需进一步了解Connection Profile各字段的说明,可参考以下文档:
https://github.com/hyperledger/fabric-samples/blob/release-1.1/balance-transfer/artifacts/network-config.yaml

新功能:支持阿里云弹性裸金属服务器(神龙)

此次区块链解决方案升级正式支持在 阿里云弹性裸金属服务器(神龙) 运行Hyperledger Fabric。阿里云弹性裸金属服务器可提供与普通物理机无差别的高计算性能,同时还提供物理机级别的安全隔离。这些特性为提升基于区块链业务系统的安全风险防范能力、加强对数据和隐私的保护等方面带来了坚实的运行环境保证。在此基础上,弹性裸金属服务器的高计算性能和弹性水平伸缩能力,结合Hyperledger Fabric代价级的性能和扩展性方面的提升,有望进一步提升区块链应用和系统的业务处理能力。

从使用方式上,我们提供了基本无缝支持的体验,用户只需在创建容器服务Kubernetes集群中选择弹性裸金属服务器(现阶段需用户提前通过工单开通弹性裸金属服务器的按量付费类型选择的白名单)作为worker节点;然后按照标准的区块链解决方案的创建部署流程操作即可。

新功能:内置容器化的区块链浏览器

区块链解决方案支持基于标准SDK开发的区块链管控工具、浏览器等与区块链网络连接。在此次升级中,区块链解决方案进一步地将Hyperledger官方开源的 Blockchain Explorer 进行了容器化改造,实现了Explorer本身以及其所依赖的MySQL数据库作为Kubernetes的service和deployment内置于解决方案中(默认启用,可设置禁用),随着区块链网络的创建实现自动配置、部署、启动和通过负载均衡(SLB)对外提供Web图形化的区块链网络业务交易监控服务。
需要说明的是,区块链浏览器的使用前提是先创建出channel(如通过cli-test.sh)。

新功能:集成阿里云日志服务

区块链解决方案原生支持容器服务控制台的日志功能,以及Kubernetes的kubectl logs命令查看功能。利用前者可以在Web界面上便捷地查看区块链网络中Peer、Orderer、CA、Kafka、Zookeeper等节点的容器日志信息,一个示例如下:


在此基础上,最新版的容器服务解决方案进一步支持与阿里云日志服务进行集成,为企业客户提供如日志存储、实时索引/查询/分析、日志告警、可视化报表等更高级的日志功能。集成的关键参数可在创建部署区块链网络的页面进行直接设置,具体使用方法可参考该方案的产品文档。

在阿里云日志服务控制台查询和分析日志的效果如下图所示。关于日志服务完整的查询分析语法以及更多高级功能说明, 可参考日志服务的 产品文档 。
新功能:删除流程数据目录处理优化

考虑到部分区块链解决方案的用户在开发测试环境需要频繁创建和删除区块链网络,为了进一步提升效率,这次方案升级优化了对数据目录清理的优化。具体来说,当区块链网络被删除(例如使用helm delete命令)的同时,相应的数据目录名会被自动添加以下后缀:-deleted-当前时间戳。之所以不直接删除,主要是为了防范人为误操作的风险,以及满足部分用户希望复用原有数据的目的。

假设原数据目录为:
/data/fabric/network01

则区块链网络删除后,数据目录会被重命名为类似如下的名字:
/data/fabric/network01-deleted-2018-03-21-041756

这样便能进一步优化用户的区块链创建-删除-创建流程上的操作体验了(因为有用户需要频繁进行此类操作进行开发调试)。此外,如需彻底清理数据目录以释放存储空间,可手工运行rm命令或结合自动化脚本很方便地实现。

总结

容器服务区块链解决方案自2017年10月正式发布以来,获得了为数众多的开发者和来自零售、金融、制造、多媒体等行业的企业用户的认可和采用,基于解决方案提供的企业级区块链开发测试环境快速地进行基于区块链的业务创新应用的打造。随着此次Hyperledger Fabric v1.1和区块链解决方案的升级,将进一步帮助我们的用户打造功能更完备、性能更高、安全性更强、易用性更好的企业级区块链业务应用。

原文链接
阅读更多干货好文,请关注扫描以下二维码:
云计算
2018-04-03 15:12:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
摘要: 全球开源区块链领域影响最为广泛的Hyperledger Fabric日前宣布了1.1版本的正式发布,带来了一系列丰富的新功能以及在安全性、性能与扩展性等方面的显著提升。阿里云容器服务区块链解决方案第一时间同步升级,在v1.1新功能的基础上,提供了弹性裸金属服务器(神龙)、内置容器化Explorer、集成阿里云日志服务等方面的增强。
作为全球开源区块链领域影响最为广泛的项目之一,超级账本近日宣布了Hyperledger Fabric v1.1的正式发布。此次升级带来了一系列丰富的新功能以及在安全性、性能与扩展性等诸多方面的显著提升。更多详情可参考下述资料: Hyperledger Fabric v1.1官方发布博客: https://www.hyperledger.org/blog/2018/03/20/hyperledger-fabric-v1-1-released Hyperledger Fabric GitHub源代码项目: https://github.com/hyperledger/fabric Hyperledger Fabric项目路线图: https://wiki.hyperledger.org/projects/fabric/roadmap
与此同时,作为对开源区块链生态的支持,阿里云容器服务区块链解决方案也进行了同步升级。这次升级不仅第一时间在Kubernetes集群上支持Hyperledger Fabric v1.1.0 GA版,并且在以下几个方面带来了新的功能增强和使用体验的优化: 支持新的Node.js类型chaincode和相关示例,以及在国内云环境落地的适配 支持chaincode级别的账本数据加密新功能和相关示例 支持新的connection profile和自动生成功能 以及其他Hyperledger Fabric v1.1新功能的支持 支持阿里云弹性裸金属服务器(神龙) 内置容器化的Hyperledger Blockchain Explorer 集成阿里云日志服务 优化区块链网络删除过程的数据目录清理
本文将对这些主要新功能进行技术方面的解读。在开始之前,我们准备了一段小视频,可以让大家快速了解在阿里云容器服务Kubernetes环境中使用该区块链解决方案的大致流程。
一分钟认识最新版容器服务区块链解决方案


最新版的区块链解决方案支持从容器服务Kubernetes的应用目录的acs-hyperledger-fabric图形界面中实现一键部署。此外在参数页面可以通过直接编辑来实现更丰富的自定义配置,如更改区块链网络拓扑、指定网络端口、功能启用/禁用等等。
在上述视频演示中,我们依次进行了以下几步操作: 配置和一键部署区块链网络 容器服务Kubernetes控制台查看和管理区块链网络的服务和容器 运行模拟转账交易智能合约的CLI测试 运行容器化、图形化的区块链浏览器 容器服务控制台查看区块链网络运行日志
需要说明的是,视频演示操作前需先完成环境准备工作,具体步骤请参见相关 文档指南 。
新功能:Node.js类型Chaincode支持
在此次Hyperledger Fabric v1.1的升级中,在原有的Golang类型之上(Java类型尚未正式宣布支持),加入了对流行的Node.js编程语言类型的智能合约(即chaincode)的支持。这将进一步扩大和丰富Hyperledger Fabric的应用开发生态,吸引更多的开发者加入到区块链应用的创新中来。
在此基础上,通过对Fabric的源代码分析,我们在区块链解决方案中解决了在国内网络环境中可能遇到的Node.js类型chaincode实例化(instantiation)过程超时失败、chaincode镜像无法创建的局限性。
如需体验Node.js类型的chaincode,可采用CLI方式或者Client SDK方式,通过参数指定chaincode语言类型即可。解决方案中提供了基于官方cli-test.sh的示例脚本、以及基于官方的balance-transfer的client SDK示例程序。
CLI方式的Node.js类型chaincode示例

Node.js类型示例chaincode源代码位置:
SDK方式的Node.js类型chaincode示例

需要说明的是,由于Node.js应用自身编译构建的特点,Hyperledger Fabric中Node.js类型的chaincode实例化(instantiation)时间相比Golang类型的要更长,所以在这个环节需要稍作等待。
新功能:Chaincode级别对账本数据的加密和签名
Hyperledger Fabric v1.1新增支持在chaincode级别对交易的数据(即StateDB里Key-Value中的Value,通过调用chaincode时传入)进行加密/解密、签名/验证。这样能实现业务应用将商业敏感数据交给区块链之后,数据全程(包括落地到账本)在原有的安全机制之上进一步添加了标准的或用户可扩展的加密算法和签名算法的保护,进一步提高了数据的安全性。
关于此功能的的官方文档介绍可参考:
http://hyperledger-fabric.readthedocs.io/en/latest/chaincode4ade.html?highlight=chaincode%20encryption#chaincode-encryption
在容器服务的区块链解决方案中,我们提供了官方的EncCC示例程序用于在CLI环境中运行,方便用户了解此功能的使用和工作机制。在该示例中,首先对键"key1"的值“value1”进行加密并记录到账本上;然后读取键"key1"并解密其值,输出显示"value1";接着,再对键“key2”的值"value2"进行加密以及数字签名并记录到账本上,最后读取键“key2”并解密、验证签名得到最终值,输出显示"value2"。
需要说明的是,使用该CLI示例程序之前,请先创建对应的channel(例如通过运行cli-tset.sh)。
示例程序源代码参考:
https://github.com/hyperledger/fabric/tree/release-1.1/examples/chaincode/go/enccc_example

新功能:Connection Profile用于Client SDK应用程序
针对不同类型的区块链应用程序有着各不相同的对区块链网络拓扑和连接信息的描述方式的问题,为了提供更为统一规范的应用连接配置,Hyperledger Fabric v1.1开始支持Connection Profile, 目前主要面向Node.js类型的Client SDK应用程序,未来有望推广到更多类型的SDK应用上。
在此次区块链方案升级中,也同步支持此功能,并且能够根据用户输入的区块链网络拓扑配置(channel, peer, orderer, ca等)、公网访问地址和端口等信息,自动生成Connection Profile应用连接配置文件(即network-config.yaml)并提供给用户下载。而在我们的Client SDK示例 balancer-transfer-app 中也通过一个 脚本 实现一键下载所有证书密钥以及Connection Profile等配置文件。
解决方案自动生成的Connection Profile的位置和部分内容示例如下:
如需进一步了解Connection Profile各字段的说明,可参考以下文档:
https://github.com/hyperledger/fabric-samples/blob/release-1.1/balance-transfer/artifacts/network-config.yaml
新功能:支持阿里云弹性裸金属服务器(神龙)
此次区块链解决方案升级正式支持在 阿里云弹性裸金属服务器(神龙) 运行Hyperledger Fabric。阿里云弹性裸金属服务器可提供与普通物理机无差别的高计算性能,同时还提供物理机级别的安全隔离。这些特性为提升基于区块链业务系统的安全风险防范能力、加强对数据和隐私的保护等方面带来了坚实的运行环境保证。在此基础上,弹性裸金属服务器的高计算性能和弹性水平伸缩能力,结合Hyperledger Fabric代价级的性能和扩展性方面的提升,有望进一步提升区块链应用和系统的业务处理能力。
从使用方式上,我们提供了基本无缝支持的体验,用户只需在创建容器服务Kubernetes集群中选择弹性裸金属服务器(现阶段需用户提前通过工单开通弹性裸金属服务器的按量付费类型选择的白名单)作为worker节点;然后按照标准的区块链解决方案的创建部署流程操作即可。
新功能:内置容器化的区块链浏览器
区块链解决方案支持基于标准SDK开发的区块链管控工具、浏览器等与区块链网络连接。在此次升级中,区块链解决方案进一步地将Hyperledger官方开源的 Blockchain Explorer 进行了容器化改造,实现了Explorer本身以及其所依赖的MySQL数据库作为Kubernetes的service和deployment内置于解决方案中(默认启用,可设置禁用),随着区块链网络的创建实现自动配置、部署、启动和通过负载均衡(SLB)对外提供Web图形化的区块链网络业务交易监控服务。

需要说明的是,区块链浏览器的使用前提是先创建出channel(如通过cli-test.sh)。
新功能:集成阿里云日志服务
区块链解决方案原生支持容器服务控制台的日志功能,以及Kubernetes的 kubectl logs 命令查看功能。利用前者可以在Web界面上便捷地查看区块链网络中Peer、Orderer、CA、Kafka、Zookeeper等节点的容器日志信息,一个示例如下:

在此基础上,最新版的容器服务解决方案进一步支持与阿里云日志服务进行集成,为企业客户提供如日志存储、实时索引/查询/分析、日志告警、可视化报表等更高级的日志功能。集成的关键参数可在创建部署区块链网络的页面进行直接设置,具体使用方法可参考该方案的产品文档。
在阿里云日志服务控制台查询和分析日志的效果如下图所示。关于日志服务完整的查询分析语法以及更多高级功能说明, 可参考日志服务的 产品文档 。
新功能:删除流程数据目录处理优化
考虑到部分区块链解决方案的用户在开发测试环境需要频繁创建和删除区块链网络,为了进一步提升效率,这次方案升级优化了对数据目录清理的优化。具体来说,当区块链网络被删除(例如使用helm delete命令)的同时,相应的数据目录名会被自动添加以下后缀: -deleted-当前时间戳 。之所以不直接删除,主要是为了防范人为误操作的风险,以及满足部分用户希望复用原有数据的目的。
假设原数据目录为: /data/fabric/network01
则区块链网络删除后,数据目录会被重命名为类似如下的名字: /data/fabric/network01-deleted-2018-03-21-041756
这样便能进一步优化用户的区块链创建-删除-创建流程上的操作体验了(因为有用户需要频繁进行此类操作进行开发调试)。此外,如需彻底清理数据目录以释放存储空间,可手工运行rm命令或结合自动化脚本很方便地实现。
总结
容器服务区块链解决方案自2017年10月正式发布以来,获得了为数众多的开发者和来自零售、金融、制造、多媒体等行业的企业用户的认可和采用,基于解决方案提供的企业级区块链开发测试环境快速地进行基于区块链的业务创新应用的打造。随着此次Hyperledger Fabric v1.1和区块链解决方案的升级,将进一步帮助我们的用户打造功能更完备、性能更高、安全性更强、易用性更好的企业级区块链业务应用。
原文链接
云计算
2018-04-03 15:02:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
摘要: 深圳云栖大会已经圆满落幕,在3月29日飞天技术汇-弹性计算、网络和CDN专场中,阿里云CDN高级技术专家周哲为我们带来了《海量短视频极速分发》的主题分享,带领我们从视频内容采集、上传、存储和分发的角度介绍整体方案,并且重点讲解短视频加速的注意事项和用户体验优化要点。
深圳云栖大会已经圆满落幕,在3月29日飞天技术汇-弹性计算、网络和CDN专场中,阿里云CDN高级技术专家周哲为我们带来了《海量短视频极速分发》的主题分享,带领我们从视频内容采集、上传、存储和分发的角度介绍整体方案,并且重点讲解短视频加速的注意事项和用户体验优化要点。
讲解从三个部分展开:短视频应用场景、阿里云短视频解决方案、阿里云对短视频用户体验的相关优化。
短视频应用场景
短视频比图片和文字包含更多的信息,在移动互联网普及的今天,短视频应用场景非常广泛。
一、 社群分享场景
用户可以录制视频,进行特效编辑处理,上传到服务器,通过社区的方式分享给好友。
二、 电商场景
商家可以快速拍摄、制作商品的短片,把商品的介绍视频发给消费者,这样可以提高购买转化率。
三、 IM聊天场景
在交流过程中,通过随拍随传的短视频,用户可以更生动的表达观点,更真实的传递情感
四、 新媒体场景
区别于传统、单一的图文展示形态,短视频可以快速收集及发布新闻,一键呈现世界
五、 体育赛事场景
集成短视频SDK后,用户可以在现场实时拍摄和剪辑,生成具有传播价值的短视频,通过分享来全网同步赛事
阿里云针对短视频的业务场景,提供了一整套技术解决方案。在主播端,提供了视频上传SDK和短视频SDK,在观众端提供视频播放SDK,在服务器端提供点播服务、OSS存储和CDN加速。
对于开发者来说,短视频解决方案的使用流程是这样的:首先主播端使用短视频SDK对用户的视频进行录制、特效处理和剪辑,然后将视频通过上传SDK上传到服务器端,在服务器端先通过点播服务进行转码以及视频处理,然后把处理好的视频存储到OSS上,做持久化保存。在播放端集成播放器SDK,播放器会向CDN请求资源,CDN如果有缓存会把缓存的数据发给客户端。如果CDN缓存没有数据会去OSS中拉取数据,在返回给客户端,并把数据缓存到CDN上。开发者使用这些技术组件,可以快速的开发短视频业务。
下面,我们逐一介绍解决方案的能力与特点。
解决方案中的视频SDK,包括短视频、上传和播放三个。短视频SDK包含人脸贴图、高级美颜的功能,在用户录制视频之后先进行本地美颜再进行上传,同时也提供专业滤镜、MV特效和裁剪压缩、添加音乐的功能,使视频剪辑大众化;上传SDK通过快速通道可以把视频传输到服务器端,支持断点续传、后台上传、多文件管理等功能;播放器SDK支持Android、IOS、flash、H5等多个平台、加密播放和安全下载等。
点播和存储服务,点播服务提供视频管理相关的功能,比如视频转码,窄带高清可以在相同的用户观感体验下,节约30%的带宽;云剪辑功能可以在线对视频进行编辑处理;同时点播服务提供丰富的数据统计,开发者利用阿里云提供的API访问实时数据。
同时,阿里云也在视频领域开放了AI技术,包括内容审核,降低违规风险;智能封面,对视频结构化处理和算法选取最优视频帧作为视频封面;标签预测,通过AI来识别视频中文字、图像、人物等信息,自动生成视频标签等。
基于短视频业务产生的海量数据,阿里云也提供了高可靠、强安全、低成本的存储服务。
为了应对短视频大规模的业务需求,阿里云CDN建设了大量的基础设施。
首先,阿里云CDN拥有全球1200多个节点,分布在全球各个国家和地区,贴近用户部署,使用户更快速的访问视频内容,使视频体验更好。同时,为了面对业务的突发流量,阿里云提供了超过80TB的带宽储备,为持续增长的业务保驾护航。并且,单节点缓存容量超过1PB,通过提供更大的缓存空间,来提升用户访问速度。
介绍完了解决方案之后,周哲老师又为大家介绍了阿里云在短视频领域的一些优化技术。
短视频用户体验的关键指标有首屏时间、播放卡顿和播放错误。
一、首屏时间是指在视频稳定播放期间,从用户点击播放到视频开始播放之间的时间差。首屏时间越长,会导致用户提前结束播放。
二、播放卡顿是指在播放过程中的不流畅情况,会严重影响用户体验。
三、播放错误是指用户播放视频时没有成功播放,或出现异常导致提前中断。
用户体验优化剖析
为了解决这些问题,我们对整个架构进行剖析。
首先,终端用户使用阿里云提供的视频SDK来请求调度服务器,调度服务器返回对应的节点IP,然后播放器通过CDN的节点IP来请求资源,CDN的边缘节点如果有缓存的话可以从本地发给播放器,如果没有缓存就去二级节点进行访问,二级节点没有缓存会去OSS或者用户源站拉取数据。
阿里云CDN有一级节点、二级节点的缓存,二级节点的作用是
第一,做资源收敛,避免相同请求同时回源,减少源站压力
第二,离边缘节点接近,在边缘节点没命中的时候,能够更快的从二级节点获取到数据
第三,提供更大的存储空间,提高命中。
同时,阿里云对于整个播放过程,进行一些用户体验相关的数据打点,通过这些数据来进行分析,得到了一些优化点。
第一就是播放器
播放器是否优化决定了终端用户的观看质量。首先,我们对播放器SDK做了相关优化,比如,通过减少首次缓冲时间,可以设置在收到视频第一个I帧就开始解码播放;提供动态缓存大小,当出现网络抖动的时候,可以动态增加缓存大小,对数据进行预加载,通过这种方式来降低卡顿率;针对播放错误,播放器SDK实现了网络重试机制,来提供播放成功率。
第二个优化是针对调度系统
用户在访问边缘节点的时候,访问调度服务器,调度服务器返回的IP地址是不是最优的网络链路,这个很大程度上影响用户对于视频的下载速度。阿里云自研了智能调度系统,可以选择用户到边缘节点的最优链路,来反馈给客户端。
第三是阿里云提供非常多的网络节点覆盖
能够让边缘节点距离用户更近,来提升用户访问速度。
第四个优化点是协议栈优化
在用户向CDN服务器获取数据时,当出现网络抖动的时候,会有下载速度降低情况产生,阿里云CDN针对TCP协议栈进行优化,通过更高效的协议栈算法来提升下载速度。
第五个优化点是窄带高清
开发者可以使用阿里云提供的窄带高清功能,来降低播放视频的带宽。
第六是缓存服务器优化
用户在访问边缘节点的时候,如果请求在边缘节点MISS会直接回源,响应时间变慢,而如果边缘节点命中,用户的下载的速度就和边缘节点缓存服务器的性能具有强关联性了,阿里云针对缓存服务器的性能做了很多优化,通过加大边缘服务器的空间和智能优化,来提升访问速率。
第七是全链路监控
阿里云也支持全链路监控,来提早发现问题,针对性优化,确保稳定服务
智能调度系统优化
这里详细介绍一下智能调度的优化,针对调度系统,阿里云做了五个方面的优化。
第一个是精准的IP数据库会持续更新
基于第三方IP库数据、whois平台和淘宝手机用户在访问数据时的一些经纬度信息,会对IP库进行主动探测,基于这些维度的数据,进行计算和生成。IP库数据的准确性会对调度系统提供数据支撑。
第二个是提供HTTPDNS服务
用户通过localdns请求调度服务器,会遇到第一个问题是:localdns和用户ip位置有不匹配问题,会出现基于localdns调度时,调度系统看到的客户端ip和用户真实ip不一致,导致调度不准确。第二个问题是:用户查询dns服务器时,在查询过程中会出现dns结果被串改的情况
通过httpdns服务,可以解决由于localdns和ip地址不匹配问题,同时也可以做到dns结果防串改,使调度更精准。
播放器基于httpdns对dns查询做预加载,并对结果做缓存,减少dns查询查询带来的首屏时间慢。
第三点是基于用户体验的自动化调度
会基于用户的客户端IP以及用户到每个节点的网络情况、当前各个节点的状态,来进行计算,通过一些机器学习的方式,来预测用户到哪个节点的网络链路为最优。这样就确保给用户IP地址返回最优的网络链路。
第四个是大数据分析,选择最优节点。
通过当前节点覆盖情况和用户访问流量趋势,进行数据预测,去选择在哪里建点,并且去计算这些节点覆盖哪些用户的网络质量是最优的,为调度系统提供数据支撑。
最后一点是基于内容调度,提高命中率。
通过302做内容粒度的调度,把相同URL调度到同一台机器上,提高命中率。
缓存性能优化
阿里云CDN针对缓存系统也做了很多优化。
通过自研的缓存系统,开发基于裸盘的缓存系统。使用内存、SSD、SATA三级缓存架构,把最热的资源存储到内存中,次热的资源存储在SSD中,冷的资源存储在SATA中,并且通过热点迁移的方式,把SATA中热的资源迁移到内存和SSD中,再通过淘汰算法,把内存和SSD中的冷的资源淘汰到SATA盘中。通过这种方式,提供内存和SSD的命中率,降低SATA盘的IO读写情况,来存储更多的冷资源,提高边缘节点利用率。
同时,自研的文件系统对IO操作做了很多优化,比如说合并IP对齐写入功能,避免内存碎片和磁盘碎片带来的IO性能严重下降。对大文件进行切片存储,分片存储到不同磁盘中,避免因单个视频文件过热导致磁盘性能下降。
通过以上的优化措施,缓存的平均命中率在95%以上,首字节时间平均低于10ms。
基于用户体验的TCP协议栈优化
第一个优化点是基于时间序的丢包发现机制,主要是在TCP协议栈里去分析TCP发包的时间序列,然后能够识别出哪些包丢掉了,快速发现丢包和快速重传。
第二个优化点是主动丢包发现机制,是指我们在发现包的时候去主动探测,避免通过超时来发现丢包,减少丢包发现的时间,提高进行重传
第三个是自适应的初始化窗口,针对不同业务场景使用不同初始化窗口大小。
第四个是更激进的拥塞避免算法,针对网络不好的情况,在协议栈中识别状态,使用更激进的发包策略,解决下载速度慢的问题。
第五个优化点是使用更小的链接超时时间,来更早的发现丢包。
基于以上的优化,我们使用第三方检测平台,对某短视频业务1.2M文件,和其他CDN厂商做性能对比测试,相同文件大小从内容下载时间上看,使用阿里云CDN有2%-6%的性能提升。其余数据也表现优异。
欢迎大家来使用阿里云点播服务、CDN和OSS存储,来搭建短视频应用。
原文链接
云计算
2018-04-03 14:28:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
摘要: 在3月29日深圳云栖大会的数据分析与可视化专场中,阿里云产品专家潘炎峰(陌停)对大数据智能分析产品 Quick BI 进行了深入的剖析。大会现场的精彩分享也赢得观众们的一直认可和热烈的反响。 Quick BI诞生于阿里巴巴集团自身对数据分析的需求过程。
在3月29日深圳云栖大会的数据分析与可视化专场中,阿里云产品专家潘炎峰(陌停)对大数据智能分析产品 Quick BI 进行了深入的剖析。大会现场的精彩分享也赢得观众们的一直认可和热烈的反响。
Quick BI诞生于阿里巴巴集团自身对数据分析的需求过程。从自研报表,到自研BI可视化,再到现在所使用的智能Quick BI,集团经历了多年的探索。从当初重度依赖专业的数据分析人才,到现在赋予一线业务人员智能化的分析工具,阿里小二们真正的做到了“数据化运营”让数据产生价值。
数据处理
现在,越来越多的企业开始数据上云,也有的行业如政府、金融因为严苛的安全需求而自建本地数据库,导致企业出现数据分散式存储的状况。而Quick BI却可以链接各种数据源,满足云上和本地的不同需求,整合为可被统一调度的数据集。
可视化展现
Quick BI的可视化能力也不容小觑,内设地图、柱图、雷达图等21种数据图表,任何场景下的报表展示均毫无压力。特别令人惊喜的是Quick BI 特有的类Excel的电子表格功能,它足以让企业数据分析人员兴奋不已,不仅延续了本地化操作的经验,也更加贴合中国式复杂报表的制作需求。
Quick BI 仪表板示例
Quick BI电子表格示例
协同与安全
除了在数据基础层面的连接、分析与展示,Quick BI还愿意为客户想的更多。协同与安全便是深钻的核心。
Quick BI 可以创建多个工作空间,每个空间的被编辑与被访问权限可以灵活设置,让空间中的不同角色,获得匹配自己的行为权限。过程中既保障了数据的私密性和安全性,又实现了数据的分享和部门的协同。
行业应用
会上了解到目前Quick BI已经广泛应用于零售、金融、互联网、媒体、医疗健康、通讯等行业,并期待着与更多领域的企业开展合作。
金融行业应用案例
传统行业应用案例
持续更新
Quick BI是一款不断更新与迭代的产品,不难发现,它正朝着以“降低技术要求、解决存储计算、智能业务感知” 的方向前行,也许不久的将来就会呈现数据分析的新生态,人人都是分析师,让数据为业务创造更多价值。
原文链接
云计算
2018-04-03 13:06:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
摘要: 在3月29日深圳云栖大会的数据分析与可视化专场中,阿里云产品专家陌停对大数据智能分析产品 Quick BI 进行了深入的剖析。大会现场的精彩分享也赢得观众们的一直认可和热烈的反响。 大数据分析之路的挑战与期望 阿里巴巴作为一家大数据公司,整个集团,从上到下都在践行数字化运营。

在3月29日深圳云栖大会的数据分析与可视化专场中,阿里云产品专家陌停对大数据智能分析产品 Quick BI 进行了深入的剖析。大会现场的精彩分享也赢得观众们的一直认可和热烈的反响。

大数据分析之路的挑战与期望

阿里巴巴作为一家大数据公司,整个集团,从上到下都在践行数字化运营。传统的大数据分析之路,正面临着临时需求多、需求响应时间长、本地化现象严重、专业人才紧缺等挑战,急需构建一个面向业务人员的自助式大数据分析工具,让业务人员自助式实现在线数据分析,助力企业业务的数据化。

Quick BI核心能力

一、如何保障高性能即席查询

大数据分析的三要素是人、数据、计算与存储,而计算存储作为大数据分析的基础能力。Quick BI兼容Oracle 、Mysql等关系数据库,来支撑小数据集的分析与处理,也兼容Hadoop等分布式数据库和云数据库。Quick BI无缝兼容阿里云数据库,包括Maxcopute、Analytic DB等数据库,能做到100G数据15秒内实现汇总与查询。

二、如何降低专业人才依赖

(1)拖拽式自助分析

贴合数据人员的数据分析思维,提供查询联动、组件联动分析、下钻联动分析等能力,并基于图表组件实现拖拽式的可视化配置能力,让无技术的业务员可以自助式实现在线大数据分析与可视化。

(2)在线电子表格

打造在线电子表格能力,提供类似Excel的拖拽式、筛选、冻结及300+函数,完全演戏Excel的操作习惯,降低业务人员的学习成本,提升数据分析能力。

三、如何保障数据访问安全

数据本身辐射的群体是有限的,基于工作空间隔离的概念,实现基于工作空间组的在线协同分析机制。基于空间角色实现功能操作的管控,实现了最细粒度的行级数据访问控制。

大数据行业分析案例

Quick BI已经广泛应用于零售、金融、互联网、媒体、医疗健康、通讯等行业,并期待着与更多领域的企业开展合作。以两个行业应用案例为切入点,阐述Quick BI的给企业赋能的价值。我们不仅仅提供大数据分析与可视化的工具,同时也将大数据分析的思维方式和手段赋能给企业。
持续创新

Quick BI是一款不断更新与迭代的产品,我们以“降低技术要求、解决存储计算、智能业务感知” 的目标,致力于打造大数据分析的新生态,实现人人都是分析师,助力企业业务数据化。

原文链接
阅读更多干货好文,请关注扫描以下二维码:
云计算
2018-04-03 11:47:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Longhorn项目是Rancher Labs推出的开源的基于云和容器部署的分布式块存储新方式。Longhorn遵循微服务的原则,利用容器将小型独立组件构建为分布式块存储,并使用容器编排来协调这些组件,形成弹性分布式系统。
自2017年4月Longhorn项目发布以来,人们对在Kubernetes集群上运行Longhorn存储就产生了极大的兴趣。近日,Longhorn v0.2版本发布了,它可支持任何Kubernetes集群的持久化存储实现!
Why Longhorn
如今,基于云和容器的部署规模日益扩大,分布式块存储系统也正变得越来越复杂,单个存储控制器上的volume数量在不断增加。2000年代初,存储控制器上的volume数量只有几十个,但现代云环境却需要数万到数百万的分布式块存储卷。存储控制器变成了高度复杂的分布式系统。
分布式块存储本身比其他形式的分布式存储(如文件系统)更简单。无论系统中有多少volume,每个volume只能由单个主机进行装载。正因如此,我们设想,是否可以将大型块存储控制器分割成多个较小的存储控制器?若想要如此分割,我们需要保证这些volume仍然是从公共磁盘池构建的,并且我们需要有办法来编排这些存储控制器,让它们可以协同工作。
为了将这一想法发挥到极限,我们创建了Longhorn项目。这是一个我们认为值得探索的方向,每个控制器上只有一个volume,这将大大简化存储控制器的设计。因为控制器软件的故障域仅限于单个volume,所以控制器若崩溃,也只会影响一个volume。
Longhorn充分利用了近年来关于如何编排大量的容器和虚拟机的核心技术。例如,Longhorn并没有构建一个可以扩展到100,000个volume的高度复杂的控制器,而是出于让存储控制器简单轻便的考虑,创建了100,000个单独的控制器。然后,我们可以利用像Swarm、Mesos和Kubernetes这样的最先进的编排系统来调度这些独立的控制器,共享一组磁盘中的资源,协同工作,形成一个弹性的分布式块存储系统。
Longhorn基于微服务的设计还有很多其他优势。因为每个volume都有自己的控制器,在升级每个volume的控制器和replica容器时,是不会导致IO操作明显的中断的。 Longhorn可以创建一个长期运行的工作来编排所有live volume的升级,同时确保不会中断系统正在进行的操作。为确保升级不会导致意外的问题,Longhorn可以选择升级一小部分volume,并在升级过程中出现问题时回滚到旧版本。这些做法在现代微服务应用中已得到广泛应用,但在存储系统中并不常见。我们希望Longhorn可以助力于微服务在存储领域的更多应用。
Longhorn功能概述 共享资源池 :将本地磁盘或安装在计算或专用存储主机中的网络存储形成共享资源池。 为容器和虚拟机创建块存储卷 :你可以指定volume的大小,IOPS的需求,以及你想要的跨主机的同步replica的数量(这里的主机是指那些为volume提供存储资源的主机)。replica是在底层磁盘或网络存储上精简配置的。 为每个volume创建一个专用的存储控制器 :这可能是与大多数现有的分布式存储系统相比,Longhorn最具特色的功能。大多数现有的分布式存储系统通常采用复杂的控制器软件来服务于从数百到数百万不等的volume。但Longhorn不同,每个控制器上只有一个volume,Longhorn将每个volume都转变成了微服务。 跨计算或存储主机调度多个replica :Longhorn会监测每一个replica的健康状况,对问题进行维修,并在必要时重新生成replica。 以Docker容器的形式操作存储控制器和replica :例如,一个volume有三个replica,就意味着有四个容器。 为每个volume分配多个存储“前端” :常见的前端包括Linux内核设备(映射到/ dev / longhorn)和iSCSI目标。 Linux内核设备适用于支持Docker volume,而iSCSI目标更适合支持QEMU / KVM和VMware volume。 创建volume快照和AWS EBS风格的备份 :您可以为每个volume创建多达254个快照,这些快照可以逐个备份到NFS或S3兼容的辅助存储中。只有更改的字节会在备份操作期间被复制和存储。 指定定期快照和备份操作的计划 :您可以指定这些操作的频率(每小时,每天,每周,每月和每年)、执行这些操作的确切时间(例如,每个星期日凌晨3:00),以及保留多少个循环快照和备份集。
支持任何Kubernetes集群的持久性存储
Longhorn v0.2 支持任何Kubernetes集群的持久性存储。一旦被部署到Kubernetes集群上,Longhorn会自动地将Kubernetes集群中所有节点上全部可用的本地存储聚为集群,产生复制的以及分布式的块存储。你可以在Longhorn volume上执行快照和备份操作,并将它们同步复制到多个节点上。
我们已经移植了Longhorn Manager作为Kubernetes Controller。所有Longhorn状态都存储为Custom Resource Definitions(自定义资源定义,CRD)。Longhorn也不需要单独的etcd服务器。另外,Longhorn Manager公开了执行Longhorn volume操作和快照 / 备份操作的API,这些API将在Longhorn UI和Kubernetes Flexvolume驱动程序执行操作的过程中使用。
运行下面的这条指令,就可以在你的Kubernetes集群上部署整个Longhorn存储系统: kubectl create -f https://raw.githubusercontent.com/rancher/longhorn/v0.2/deploy/longhorn.yaml
如果你使用的是GKE,请参考这里: https://github.com/rancher/longhorn/blob/master/README.md#google-kubernetes-engine
部署之后,你可以在UI界面通过查看Kubernetes服务找到适合的IP:kubectl -n longhorn-system get svc
现在你可以使用 100.200.200.123 或通过 :12345 访问UI。
Longhorn提供了Kubernetes的完全集成。
你可以像下面这样通过Longhorn用卷备份创建一个pod: apiVersion: v1 kind: Pod metadata: name: volume-test namespace: default spec: containers: - name: volume-test image: nginx:stable-alpine imagePullPolicy: IfNotPresent volumeMounts: - name: voll mountPath: /data ports: - containerPort: 80 volumes: - name: voll flexVolume: driver: "rancher.io/longhorn" fsType: "ext4" options: size: "2Gi" numberOfReplicas: "3" staleReplicaTimeout: "20" fromBackup: ""
Longhorn还支持动态的provisioner。比如,你可以在Kubernetes像这样定义一个StorageClass: kind: StorageClass apiVersion: storage.k8s.io/v1 metadata: name: longhorn provisioner: rancher.io/longhorn parameters: numberOfReplicas: "3" staleReplicaTimeout: "30" fromBackup: ""
接着创建一个PVC(PersistentVolumeClaim)并在Pod中使用它: apiVersion: v1 kind: PersistentVolumeClaim metadata: name: longhorn-volv-pvc spec: accessModes: - ReadWriteOnce storageClassName: longhorn resources: requests: storage: 2Gi --- apiVersion: v1 kind: Pod metadata: name: volume-test namespace: default spec: containers: - name: volume-test image: nginx:stable-alpine imagePullPolicy: IfNotPresent volumeMounts: - name: volv mountPath: /data ports: - containerPort: 80 volumes: - name: volv persistentVolumeClaim: claimName: longhorn-volv-pvc
一切开源
始终秉承开源理念的Rancher Labs,推出的Longhorn依然是100%的开源软件。您可以在GitHub上下载Longhorn: https://github.com/rancher/longhorn
直到今日,Longhorn依然在持续不断的优化更新之中。欢迎在GitHub上或Rancher微信群中为Rancher团队提供宝贵的意见与反馈❤️
云计算
2018-04-03 09:29:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
原文链接: http://blog.geekidentity.com/k8s/kops/install-k8s-with-kops-in-china/ 因为一些众所周知的原因,AWS中国区并没有k8s集群,因此我们需要自己安装k8s。而k8s官方提供了一个工具 kops 来帮助我们快速地在AWS上创建k8s集群。下面是使用kops在AWS创建集群的详细过程。
安装kops (Binaries)
我们建议使用一台低配服务器作为k8s的管理机,在上面安装kops等管理工具。
从github上下载已经编译好的二进制文件 wget -O kops https://github.com/kubernetes/kops/releases/download/$(curl -s https://api.github.com/repos/kubernetes/kops/releases/latest | grep tag_name | cut -d '"' -f 4)/kops-linux-amd64 chmod +x ./kops sudo mv ./kops /usr/local/bin/
安装其它依赖
kubectl
kubectl是管理和操作Kubernetes集群的CLI工具。
从kubernetes官方kubectl获取发布版本: wget -O kubectl https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl chmod +x ./kubectl sudo mv ./kubectl /usr/local/bin/kubectl
安装AWS CLI 工具
awscli是用Python写的,安装Python和pip后直接运行下面命令就可以了。 pip install awscli
创建账号
在 1.6.2 版本之前,通过 kops 部署 K8s 集群,需要使用 AWS 的 Route53 来提供 DNS 服务的功能。但从 1.6.2 版本开始,kops 支持部署基于 gossip 的集群,不再依赖 Route53,这让部署操作变得更加简单。
配置AWS 账号,使用该账号为kops创建专用账号: $ aws configure AWS Access Key ID [None]: AWS Secret Access Key [None]: Default region name [None]: cn-north-1 Default output format [None]: json
为了使用 kops 部署集群,还需要为 kops 创建一个 IAM 用户 kops ,并分配相应的权限: $ aws iam create-group --group-name kops $ aws iam attach-group-policy --policy-arn arn:aws:iam::aws:policy/AmazonEC2FullAccess --group-name kops $ aws iam attach-group-policy --policy-arn arn:aws:iam::aws:policy/AmazonRoute53FullAccess --group-name kops $ aws iam attach-group-policy --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess --group-name kops $ aws iam attach-group-policy --policy-arn arn:aws:iam::aws:policy/IAMFullAccess --group-name kops $ aws iam attach-group-policy --policy-arn arn:aws:iam::aws:policy/AmazonVPCFullAccess --group-name kops $ aws iam create-user --user-name kops $ aws iam add-user-to-group --user-name kops --group-name kops
为 kops 用户创建密钥: $ aws iam create-access-key --user-name kops
上面的命令会返回 kops 用户的 AccessKeyID 和 SecretAccessKey 。接着我们就可以更新 awscli 的配置,让它使用新创建的 kops 用户的密钥: $ aws configure AWS Access Key ID [None]: AWS Secret Access Key [None]: Default region name [None]: cn-north-1 Default output format [None]: json
同时还需要将 kops 用户的密钥导出到命令行的环境变量: $ export AWS_ACCESS_KEY_ID=$(aws configure get aws_access_key_id) $ export AWS_SECRET_ACCESS_KEY=$(aws configure get aws_secret_access_key) $ export AWS_REGION=$(aws configure get region)
最后是生成 SSH 密钥: $ ssh-keygen
配置 S3
需要注意,为了让 kops 创建基于 gossip 的集群,集群的命名需要使用 .k8s.local 作为后缀,例如,这里我们将集群命名为 cluster.k8s.local : $ export NAME=cluster.k8s.local
接着创建一个 S3 bucket,用户存储集群的数据,例如,这里我们将这个 bucket 命名为 cluster.k8s.local-state.ym : $ aws s3api create-bucket --bucket ${NAME}-state-store --create-bucket-configuration LocationConstraint=$AWS_REGION $ export KOPS_STATE_STORE=s3://cluster.k8s.local-state-store
准备kops ami
我们必须建立自己的AMI,因为AWS中国地区没有官方的kops ami。
创建集群
下面的命令会创建集群的配置文件,并不会真正地创建集群:
注意:kops-1.8.1不支持中国宁夏区,只支持北京区, $ kops create cluster \ --name=${NAME} \ --image=ami-089b06f993df09d53 \ --zones=cn-north-1a \ --master-count=1 \ --master-size="t2.micro" \ --node-count=1 \ --node-size="t2.micro" \ --vpc= \ --subnets= \ --networking=calico \ --ssh-public-key="~/.ssh/id_rsa.pub"
对于网络模型,使用calico,因为在线上都会自己进行网络规划,当使用k8s默认的kubenet时,k8s会修改AWS路由表,这意味着k8s需要有自己的路由表所有需要有自己的子网,如果在生产环境已经做好了网络规划,使用指定subnet,k8s网络将无法正常运行。
在创建集群之前,可以检查集群的配置文件是否正确: $ kops edit cluster ${NAME}
在AWS上我们通常使用自己的密钥连接服务器   ... spec: sshKeyName: ...
因为一些网站被墙,因此建议使用 代理 搭建集群。 ... spec: egressProxy: httpProxy: host: http-proxy port: port excludes: amazonaws.com.cn,amazonaws.cn,aliyun.cn,aliyuncs.com ...
还可以指定docker版本 ... spec: docker: logDriver: json-file version: 17.03.2-ce ...
如果确认没问题,就可以使用下面的命令创建集群: $ kops update cluster ${NAME} --yes
​ 创建集群之后,需要一段时间等待集群的初始化,等待集群起来之后,可以验证集群的状况: $ kops validate cluster
  前面已经安装好了 kubectl 工具,这里也可以使用 kubectl 检查集群状况: $ kubectl get nodes
销毁集群
  在销毁集群之前,需要先确认一下 kops 会删除哪些资源: $ kops delete cluster --name ${NAME}
  如果确认没问题,就可以真正删除集群: $ kops delete cluster --name ${NAME} --yes
参考资料 kops install How to use kops in AWS China Region 使用 kops 在 AWS 部署 Kubernetes 集群
云计算
2018-04-02 21:15:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
摘要: 针对不同数据库间数据实时同步难的问题,日前,阿里云宣布推出混合云数据同步一站式解决方案,便于广大云产品用户实现实时数据同步的混合云支持,更为方便的是,该功能让本地Oracle也能实现与云上数据库的实时同步。

针对不同数据库间数据实时同步难的问题,日前,阿里云宣布推出混合云数据同步一站式解决方案,便于广大云产品用户实现实时数据同步的混合云支持,更为方便的是,该功能让本地Oracle也能实现与云上数据库的实时同步。

目前,很多用户有云下或其他厂商的Oracle、MySQL到阿里云RDS或ECS自建数据库间的数据实时同步需求。DTS混合云数据同步功能,适用于多种用户应用场景,包括:

1、业务灰度切换

业务系统比较复杂时,不能一次性切换到阿里云。此时,业务可以按业务模块、区域或用户等维度逐步切换上云。业务上云过程中,通过DTS进行云上云下或跨云厂商数据同步,当流量切换异常时,可快速回滚业务,有效控制业务迁移风险。

2、数据灾备

即使是最可靠的服务提供商,也可能遇到倒霉的一天,为保证业务连续性,业务可以通过DTS混合云同步功能,在阿里云上快速搭建本地业务中心或其他云厂商的业务中心对应的数据灾备中心,当业务中心出现故障时,快速将业务切换到容灾中心,秒级恢复业务。

3、业务弹性扩展

因为云的灵活性、可扩展性及低成本,业务在大促等突发流量时,可以在阿里云弹性扩容并支持部分业务流量。此时,业务可以使用DTS混合云同步实现云上云下数据同步,保证业务访问本地化,有效提升用户体验。

4、阿里云云BI

阿里云提供一整套低成本、高可靠的大数据产品,用户可以通过DTS混合云同步功能,将线下数据库的业务更新实时同步到阿里云大数据产品,并基于阿里云大数据产品,快速、低成本得搭建自己的大数据应用及平台。

对于云下或云厂商的数据库,阿里云此次发布的解决方案,可以支持通过专线进行数据传输,兼顾了传输性能及数据安全性。包括:

Oracle到MySQL或DRDS的数据实时同步。
Oracle到OLAP(MaxCompute、分析型数据库AnalyticDB)的数据实时同步。
MySQL实例间的单双向数据同步。
MySQL到OLAP(MaxCompute、分析型数据库AnalyticDB、Datahub)的数据实时同步。

数据同步架构图如下:

据了解,目前该功能在国内处于领先位置,而且Oracle数据同步,及数据双向同步功能,目前国内仅有阿里云数据传输服务 DTS支持。

使用也十分方便,仅需三步: 登录数据传输服务DTS购买界面,购买数据同步实例 进入DTS控制台,配置需要同步的源实例及目标实例的连接方式、选择需要同步的库或表

配置完成后,进行启动前的预检查,预检查成功后,DTS自动启动同步实例。
了解更多,欢迎点击
https://www.aliyun.com/product/dts

有话想说?请点击下方【聚能聊话题】参与聊天哦,更有丰富奖品等你拿!
https://yq.aliyun.com/roundtable/65838?spm=a2c4e.11154000.rtmain.2.3bfb7bddeItoiQ

原文链接
阅读更多干货好文,请关注扫描以下二维码:
云计算
2018-04-02 15:39:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
摘要: 近日,阿里云宣布云解析PrivateZone正式对外公测,该产品是基于阿里云专有网络VPC(Virtual Private Cloud)环境的私有域名解析和管理服务,使用户在自定义的一个或多个专有网络VPC中快速构建DNS系统,将私有域名映射到IP地址。

近日,阿里云宣布云解析PrivateZone正式对外公测,该产品是基于阿里云专有网络VPC(Virtual Private Cloud)环境的私有域名解析和管理服务,使用户在自定义的一个或多个专有网络VPC中快速构建DNS系统,将私有域名映射到IP地址。

通过PrivateZone,用户可以方便地使用私有域名记录来管理VPC中的ECS主机名、SLB、OSS等阿里云资源,而这些私有域名在VPC之外将无法访问。此外,还可以通过专线或VPN等连接方式,将用户的VPC与传统数据中心相连,实现传统数据中心与阿里云VPC之间通过私有域名进行资源互相访问,给广大运维工程师、系统工程师、网络工程师等技术人员带来福音。

下面通过一张图来了解一下吧

原文链接
阅读更多干货好文,请关注扫描以下二维码:
云计算
2018-04-02 15:08:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
摘要: 近日,阿里云宣布云解析PrivateZone正式对外公测,该产品是基于阿里云专有网络VPC(Virtual Private Cloud)环境的私有域名解析和管理服务,使用户在自定义的一个或多个专有网络VPC中快速构建DNS系统,将私有域名映射到IP地址。
近日,阿里云宣布云解析PrivateZone正式对外公测,该产品是基于阿里云专有网络VPC(Virtual Private Cloud)环境的私有域名解析和管理服务,使用户在自定义的一个或多个专有网络VPC中快速构建DNS系统,将私有域名映射到IP地址。
通过PrivateZone,用户可以方便地使用私有域名记录来管理VPC中的ECS主机名、SLB、OSS等阿里云资源,而这些私有域名在VPC之外将无法访问。此外,还可以通过专线或VPN等连接方式,将用户的VPC与传统数据中心相连,实现传统数据中心与阿里云VPC之间通过私有域名进行资源互相访问,给广大运维工程师、系统工程师、网络工程师等技术人员带来福音。
下面通过一张图来了解一下吧
云计算
2018-04-02 15:02:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Rancher 2.0 Beta版即将发布,这一重要版本所包含的许多新功能,将真正改善用户部署和管理应用程序的方式。此次培训将demo Rancher 2.0 Beta版及其所有用户级功能,包括如何在Rancher 2.0中部署应用程序,如何使用新的监控、日志和Pipeline等。
为了让更多朋友更快完成容器部署管理从入门到进阶的全过程,Rancher Labs China Team自2017年起开始举办容器技术在线培训。前两季、累计11期培训吸引超过1300名容器技术爱好者加入,培训视频总观看量超过15000次。
2018年3月15日开始,第三季Rancher Online Training Program华丽回归!每两周一期,内容从Docker入门与进阶,到Kubernetes入门与进阶、容器存储、网络、CI/CD等,一应俱全。
4月19日晚20:30,第三期「使用Rancher 2.0管理Kuberentes集群」来了!
一期不落参加全部培训、并在整季培训完结后通过能力测试的朋友,将获得Rancher Labs官方颁发的培训认证证书!
第三期培训内容
我们的培训不变的宗旨与方式是:Demo, Demo, and more Demo!
第三期培训的议程为: 各种模式的Kubernetes集群的安装 跨集群的统一用户管理 集群内的Namespace、StorageClass和Workload等资源的管理 基于helm的应用商店 Rancher Pipeline、监控报警和日志
参与方式
第一步:
扫描下方二维码,在跳转界面中点击“立即报名”,填写资料即可注册成功:
第二步:
培训当晚仍进入报名时的链接,即可参与培训。
注意:
1、若您未注册过「IT大咖说」平台,需先行注册。
2、通过手机或PC均可观看直播,但只有PC端有语音互动功能,手机端只能使用文字互动。
第二期培训回顾
在第二期培训中,讲师讲授的内容为:
一、初步认识kubernetes及其基本概念 cluster pod 常见controller label
二、kubernetes的架构 master slave
三、kubernetes的部署及基本使用 部署方式:源码安装,kubeadm,RKE 集群操作:集群健康状态,查看pod详情,创建及删除deployment,创建及删除service 应用操作:部署应用,弹性伸缩,滚动升级
第二期培训视频回放地址为:
http://www.itdks.com/liveevent/detail/10609
讲师PPT下载方式:
关注微信公众号(RancherLabs),并在后台回复“培训0329”
4月19日晚八点半,不见不散啦~
云计算
2018-04-02 09:48:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
如果 Kubernetes 部署在诸如 AWS、GCE、Azure 等公有云上,可以直接使用云硬盘作为 Volume,下面是 AWS Elastic Block Store 的例子:
要在 Pod 中使用 ESB volume,必须先在 AWS 中创建,然后通过 volume-id 引用。其他云硬盘的使用方法可参考各公有云厂商的官方文档。
Kubernetes Volume 也可以使用主流的分布式存,比如 Ceph、GlusterFS 等,下面是 Ceph 的例子:
Ceph 文件系统的 /some/path/in/side/cephfs 目录被 mount 到容器路径 /test-ceph。
相对于 emptyDir 和 hostPath,这些 Volume 类型的最大特点就是不依赖 Kubernetes。Volume 的底层基础设施由独立的存储系统管理,与 Kubernetes 集群是分离的。数据被持久化后,即使整个 Kubernetes 崩溃也不会受损。
当然,运维这样的存储系统通常不是项简单的工作,特别是对可靠性、高可用和扩展性有较高要求时。
Volume 提供了非常好的数据持久化方案,不过在可管理性上还有不足。下一节我们将学习具有更高管理性的存储方案:PersistentVolume & PersistentVolumeClaim。
书籍:
1.《每天5分钟玩转Kubernetes》
https://item.jd.com/26225745440.html
2.《每天5分钟玩转Docker容器技术》
https://item.jd.com/16936307278.html
3.《每天5分钟玩转OpenStack》
https://item.jd.com/12086376.html
云计算
2018-04-02 05:33:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
构建环境: 宿主机操作系统为 Windows 10 X64 ,虚拟机软件为 VMware WorkStation 14.0.0 ,网卡 IP 为 192.168.195.1 ,使用 ShadowSocket 的端口为 1080 (需要在 Windows 控制面板的防火墙高级功能中放开相应端口访问权限); 虚拟机中安装 CentOS Linux release 7.4.1708 发行版,双核 3G 内存, 网卡0 使用 NAT 网络, IP 为 192.168.195.131 , 网卡1 为 Host Only 网络, IP 为 192.168.162.128 。
安装和配置Docker服务
安装Docker软件包 如果有的话,卸载旧的 Docker ,否则可能会不兼容: $ yum remove -y docker docker-io docker-selinux python-docker-py 新增 Docker 的 Yum 仓库: $ vi /etc/yum.repos.d/docker.repo [dockerrepo] name=Docker Repository baseurl=https://yum.dockerproject.org/repo/main/centos/$releasever/ enabled=1 gpgcheck=1 gpgkey=https://yum.dockerproject.org/gpg 安装 Docker 软件包: $ yum update $ yum install -y epel-release $ yum install -y docker-engine docker-engine-selinux
配置国内镜像加速 使用阿里的 Docker 镜像服务(也可以自己去申请一个地址): $ mkdir -p /etc/docker $ vi /etc/docker/daemon.json { "registry-mirrors": ["https://7g5a4z30.mirror.aliyuncs.com"] } 重启 Docker 服务: $ systemctl daemon-reload && systemctl enable docker && systemctl restart docker && systemctl status docker 检查镜像服务是否正常: $ docker run --rm hello-world Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://cloud.docker.com/ For more examples and ideas, visit: https://docs.docker.com/engine/userguide/
启动Registry服务
这里使用 localhost 作为 Registry 服务的地址,如果要想在局域网内使用,需要改成本机 IP 或者一个可以被解析的名字。 运行 Register 容器,映射到 4000 端口: $ docker run -d --name registry --restart=always -p 4000:5000 -v /opt/registry:/var/lib/registry registry:2 修改 Docker 服务配置,信任本地 Registry 服务: $ vi /usr/lib/systemd/system/docker.service ... #ExecStart=/usr/bin/dockerd ExecStart=/usr/bin/dockerd --insecure-registry localhost:4000 ... 重启 Docker 服务: $ systemctl daemon-reload && systemctl restart docker 测试 Registry 服务是否正常: $ curl -X GET http://localhost:4000/v2/_catalog {"repositories":[]} 推送一个镜像到 Registry 服务器中: $ docker pull centos:7 $ docker tag centos:7 localhost:4000/centos:7 查看推送到 Registry 的镜像是否正常: $ curl -X GET http://localhost:4000/v2/_catalog {"repositories":["centos"]}
安装和配置Kolla
获取Kolla源码 $ mkdir -pv /opt/kolla $ cd /opt/kolla $ git clone https://github.com/openstack/kolla $ cd kolla $ git checkout -b devel/pike remotes/origin/stable/pike
###安装依赖软件 $ pip install pyopenssl tox $ pip install -r requirements.txt -r test-requirements.txt
生成默认配置 $ tox -e genconfig $ mkdir -pv /etc/kolla/ $ cp -v etc/kolla/kolla-build.conf /etc/kolla/
生成Dockerfile 使用 Pike 版本的默认配置生成 source 类型的 Dockerfile : $ python tools/build.py -t source --template-only --work-dir=.. 查看 Base 镜像的 Dockerfile : $ cat ../docker/base/Dockerfile
主要看 BEGIN REPO ENABLEMENT 和 END REPO ENABLEMENT 之间的内容: FROM centos:7 LABEL maintainer="Kolla Project (https://launchpad.net/kolla)" name="base" build-date="20180327" RUN groupadd --force --gid 42401 ansible \ && useradd -M --shell /usr/sbin/nologin --uid 42401 --gid 42401 ansible \ && groupadd --force --gid 42402 aodh \ && useradd -M --shell /usr/sbin/nologin --uid 42402 --gid 42402 aodh \ && groupadd --force --gid 42403 barbican \ && useradd -M --shell /usr/sbin/nologin --uid 42403 --gid 42403 barbican \ && groupadd --force --gid 42404 bifrost \ && useradd -M --shell /usr/sbin/nologin --uid 42404 --gid 42404 bifrost \ && groupadd --force --gid 42471 blazar \ && useradd -M --shell /usr/sbin/nologin --uid 42471 --gid 42471 blazar \ && groupadd --force --gid 42405 ceilometer \ && useradd -M --shell /usr/sbin/nologin --uid 42405 --gid 42405 ceilometer \ && groupadd --force --gid 64045 ceph \ && useradd -M --shell /usr/sbin/nologin --uid 64045 --gid 64045 ceph \ && groupadd --force --gid 42406 chrony \ && useradd -M --shell /usr/sbin/nologin --uid 42406 --gid 42406 chrony \ && groupadd --force --gid 42407 cinder \ && useradd -M --shell /usr/sbin/nologin --uid 42407 --gid 42407 cinder \ && groupadd --force --gid 42408 cloudkitty \ && useradd -M --shell /usr/sbin/nologin --uid 42408 --gid 42408 cloudkitty \ && groupadd --force --gid 42409 collectd \ && useradd -M --shell /usr/sbin/nologin --uid 42409 --gid 42409 collectd \ && groupadd --force --gid 42410 congress \ && useradd -M --shell /usr/sbin/nologin --uid 42410 --gid 42410 congress \ && groupadd --force --gid 42411 designate \ && useradd -M --shell /usr/sbin/nologin --uid 42411 --gid 42411 designate \ && groupadd --force --gid 42464 dragonflow \ && useradd -M --shell /usr/sbin/nologin --uid 42464 --gid 42464 dragonflow \ && groupadd --force --gid 42466 ec2api \ && useradd -M --shell /usr/sbin/nologin --uid 42466 --gid 42466 ec2api \ && groupadd --force --gid 42412 elasticsearch \ && useradd -M --shell /usr/sbin/nologin --uid 42412 --gid 42412 elasticsearch \ && groupadd --force --gid 42413 etcd \ && useradd -M --shell /usr/sbin/nologin --uid 42413 --gid 42413 etcd \ && groupadd --force --gid 42474 fluentd \ && useradd -M --shell /usr/sbin/nologin --uid 42474 --gid 42474 fluentd \ && groupadd --force --gid 42414 freezer \ && useradd -M --shell /usr/sbin/nologin --uid 42414 --gid 42414 freezer \ && groupadd --force --gid 42415 glance \ && useradd -M --shell /usr/sbin/nologin --uid 42415 --gid 42415 glance \ && groupadd --force --gid 42416 gnocchi \ && useradd -M --shell /usr/sbin/nologin --uid 42416 --gid 42416 gnocchi \ && groupadd --force --gid 42417 grafana \ && useradd -M --shell /usr/sbin/nologin --uid 42417 --gid 42417 grafana \ && groupadd --force --gid 42454 haproxy \ && useradd -M --shell /usr/sbin/nologin --uid 42454 --gid 42454 haproxy \ && groupadd --force --gid 42418 heat \ && useradd -M --shell /usr/sbin/nologin --uid 42418 --gid 42418 heat \ && groupadd --force --gid 42420 horizon \ && useradd -M --shell /usr/sbin/nologin --uid 42420 --gid 42420 horizon \ && groupadd --force --gid 42421 influxdb \ && useradd -M --shell /usr/sbin/nologin --uid 42421 --gid 42421 influxdb \ && groupadd --force --gid 42422 ironic \ && useradd -M --shell /usr/sbin/nologin --uid 42422 --gid 42422 ironic \ && groupadd --force --gid 42461 ironic-inspector \ && useradd -M --shell /usr/sbin/nologin --uid 42461 --gid 42461 ironic-inspector \ && groupadd --force --gid 42423 kafka \ && useradd -M --shell /usr/sbin/nologin --uid 42423 --gid 42423 kafka \ && groupadd --force --gid 42458 karbor \ && useradd -M --shell /usr/sbin/nologin --uid 42458 --gid 42458 karbor \ && groupadd --force --gid 42425 keystone \ && useradd -M --shell /usr/sbin/nologin --uid 42425 --gid 42425 keystone \ && groupadd --force --gid 42426 kibana \ && useradd -M --shell /usr/sbin/nologin --uid 42426 --gid 42426 kibana \ && groupadd --force --gid 42400 kolla \ && useradd -M --shell /usr/sbin/nologin --uid 42400 --gid 42400 kolla \ && groupadd --force --gid 42469 kuryr \ && useradd -M --shell /usr/sbin/nologin --uid 42469 --gid 42469 kuryr \ && groupadd --force --gid 42473 libvirt \ && useradd -M --shell /usr/sbin/nologin --uid 42473 --gid 42473 libvirt \ && groupadd --force --gid 42428 magnum \ && useradd -M --shell /usr/sbin/nologin --uid 42428 --gid 42428 magnum \ && groupadd --force --gid 42429 manila \ && useradd -M --shell /usr/sbin/nologin --uid 42429 --gid 42429 manila \ && groupadd --force --gid 42457 memcached \ && useradd -M --shell /usr/sbin/nologin --uid 42457 --gid 42457 memcached \ && groupadd --force --gid 42430 mistral \ && useradd -M --shell /usr/sbin/nologin --uid 42430 --gid 42430 mistral \ && groupadd --force --gid 42431 monasca \ && useradd -M --shell /usr/sbin/nologin --uid 42431 --gid 42431 monasca \ && groupadd --force --gid 65534 mongodb \ && useradd -M --shell /usr/sbin/nologin --uid 42432 --gid 65534 mongodb \ && groupadd --force --gid 42433 murano \ && useradd -M --shell /usr/sbin/nologin --uid 42433 --gid 42433 murano \ && groupadd --force --gid 42434 mysql \ && useradd -M --shell /usr/sbin/nologin --uid 42434 --gid 42434 mysql \ && groupadd --force --gid 42435 neutron \ && useradd -M --shell /usr/sbin/nologin --uid 42435 --gid 42435 neutron \ && groupadd --force --gid 42436 nova \ && useradd -M --shell /usr/sbin/nologin --uid 42436 --gid 42436 nova \ && groupadd --force --gid 42470 novajoin \ && useradd -M --shell /usr/sbin/nologin --uid 42470 --gid 42470 novajoin \ && groupadd --force --gid 42437 octavia \ && useradd -M --shell /usr/sbin/nologin --uid 42437 --gid 42437 octavia \ && groupadd --force --gid 42462 odl \ && useradd -M --shell /usr/sbin/nologin --uid 42462 --gid 42462 odl \ && groupadd --force --gid 42438 panko \ && useradd -M --shell /usr/sbin/nologin --uid 42438 --gid 42438 panko \ && groupadd --force --gid 42472 prometheus \ && useradd -M --shell /usr/sbin/nologin --uid 42472 --gid 42472 prometheus \ && groupadd --force --gid 42465 qdrouterd \ && useradd -M --shell /usr/sbin/nologin --uid 42465 --gid 42465 qdrouterd \ && groupadd --force --gid 42427 qemu \ && useradd -M --shell /usr/sbin/nologin --uid 42427 --gid 42427 qemu \ && groupadd --force --gid 42439 rabbitmq \ && useradd -M --shell /usr/sbin/nologin --uid 42439 --gid 42439 rabbitmq \ && groupadd --force --gid 42440 rally \ && useradd -M --shell /usr/sbin/nologin --uid 42440 --gid 42440 rally \ && groupadd --force --gid 42460 redis \ && useradd -M --shell /usr/sbin/nologin --uid 42460 --gid 42460 redis \ && groupadd --force --gid 42441 sahara \ && useradd -M --shell /usr/sbin/nologin --uid 42441 --gid 42441 sahara \ && groupadd --force --gid 42442 searchlight \ && useradd -M --shell /usr/sbin/nologin --uid 42442 --gid 42442 searchlight \ && groupadd --force --gid 42443 senlin \ && useradd -M --shell /usr/sbin/nologin --uid 42443 --gid 42443 senlin \ && groupadd --force --gid 42467 sensu \ && useradd -M --shell /usr/sbin/nologin --uid 42467 --gid 42467 sensu \ && groupadd --force --gid 42468 skydive \ && useradd -M --shell /usr/sbin/nologin --uid 42468 --gid 42468 skydive \ && groupadd --force --gid 42444 solum \ && useradd -M --shell /usr/sbin/nologin --uid 42444 --gid 42444 solum \ && groupadd --force --gid 42445 swift \ && useradd -M --shell /usr/sbin/nologin --uid 42445 --gid 42445 swift \ && groupadd --force --gid 42446 tacker \ && useradd -M --shell /usr/sbin/nologin --uid 42446 --gid 42446 tacker \ && groupadd --force --gid 42447 td-agent \ && useradd -M --shell /usr/sbin/nologin --uid 42447 --gid 42447 td-agent \ && groupadd --force --gid 42448 telegraf \ && useradd -M --shell /usr/sbin/nologin --uid 42448 --gid 42448 telegraf \ && groupadd --force --gid 42449 trove \ && useradd -M --shell /usr/sbin/nologin --uid 42449 --gid 42449 trove \ && groupadd --force --gid 42459 vitrage \ && useradd -M --shell /usr/sbin/nologin --uid 42459 --gid 42459 vitrage \ && groupadd --force --gid 42450 vmtp \ && useradd -M --shell /usr/sbin/nologin --uid 42450 --gid 42450 vmtp \ && groupadd --force --gid 42451 watcher \ && useradd -M --shell /usr/sbin/nologin --uid 42451 --gid 42451 watcher \ && groupadd --force --gid 42452 zaqar \ && useradd -M --shell /usr/sbin/nologin --uid 42452 --gid 42452 zaqar \ && groupadd --force --gid 42453 zookeeper \ && useradd -M --shell /usr/sbin/nologin --uid 42453 --gid 42453 zookeeper \ && groupadd --force --gid 42463 zun \ && useradd -M --shell /usr/sbin/nologin --uid 42463 --gid 42463 zun LABEL kolla_version="5.0.2" ENV KOLLA_BASE_DISTRO=centos \ KOLLA_INSTALL_TYPE=source \ KOLLA_INSTALL_METATYPE=mixed #### Customize PS1 to be used with bash shell COPY kolla_bashrc /tmp/ RUN cat /tmp/kolla_bashrc >> /etc/skel/.bashrc \ && cat /tmp/kolla_bashrc >> /root/.bashrc # PS1 var when used /bin/sh shell ENV PS1="$(tput bold)($(printenv KOLLA_SERVICE_NAME))$(tput sgr0)[$(id -un)@$(hostname -s) $(pwd)]$ " # For RPM Variants, enable the correct repositories - this should all be done # in the base image so repos are consistent throughout the system. This also # enables to provide repo overrides at a later date in a simple fashion if we # desire such functionality. I think we will :) RUN CURRENT_DISTRO_RELEASE=$(awk '{match($0, /[0-9]+/,version)}END{print version[0]}' /etc/system-release); \ if [ $CURRENT_DISTRO_RELEASE != "7" ]; then \ echo "Only release '7' is supported on centos"; false; \ fi \ && cat /tmp/kolla_bashrc >> /etc/bashrc \ && sed -i 's|^\(override_install_langs=.*\)|# \1|' /etc/yum.conf COPY yum.conf /etc/yum.conf #### BEGIN REPO ENABLEMENT COPY elasticsearch.repo /etc/yum.repos.d/elasticsearch.repo COPY grafana.repo /etc/yum.repos.d/grafana.repo COPY influxdb.repo /etc/yum.repos.d/influxdb.repo COPY kibana.yum.repo /etc/yum.repos.d/kibana.yum.repo COPY MariaDB.repo /etc/yum.repos.d/MariaDB.repo COPY opendaylight.repo /etc/yum.repos.d/opendaylight.repo COPY td.repo /etc/yum.repos.d/td.repo COPY zookeeper.repo /etc/yum.repos.d/zookeeper.repo RUN yum -y install http://repo.percona.com/release/7/RPMS/x86_64/percona-release-0.1-4.noarch.rpm && yum clean all RUN rpm --import http://yum.mariadb.org/RPM-GPG-KEY-MariaDB \ && rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-Percona \ && rpm --import https://packages.elastic.co/GPG-KEY-elasticsearch \ && rpm --import https://repos.influxdata.com/influxdb.key \ && rpm --import https://packagecloud.io/gpg.key \ && rpm --import https://grafanarel.s3.amazonaws.com/RPM-GPG-KEY-grafana \ && rpm --import https://packages.treasuredata.com/GPG-KEY-td-agent RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 RUN yum -y install epel-release yum-plugin-priorities centos-release-ceph-jewel centos-release-openstack-pike centos-release-opstools centos-release-qemu-ev && yum clean all RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-OpsTools \ && rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-Storage \ && rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-Virtualization \ && yum clean all #### END REPO ENABLEMENT # Update packages RUN yum -y install curl iproute iscsi-initiator-utils lvm2 scsi-target-utils sudo tar which && yum clean all COPY set_configs.py /usr/local/bin/kolla_set_configs COPY start.sh /usr/local/bin/kolla_start COPY sudoers /etc/sudoers COPY curlrc /root/.curlrc RUN curl -sSL https://github.com/Yelp/dumb-init/releases/download/v1.1.3/dumb-init_1.1.3_amd64 -o /usr/local/bin/dumb-init \ && chmod +x /usr/local/bin/dumb-init \ && sed -i 's|#!|#!/usr/local/bin/dumb-init |' /usr/local/bin/kolla_start RUN touch /usr/local/bin/kolla_extend_start \ && chmod 755 /usr/local/bin/kolla_start /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_set_configs \ && chmod 440 /etc/sudoers \ && mkdir -p /var/log/kolla \ && chown :kolla /var/log/kolla \ && chmod 2775 /var/log/kolla \ && rm -f /tmp/kolla_bashrc CMD ["kolla_start"]
创建本地Yum仓库
配置同步镜像 启动一个用于同步远端仓库的 CentOS 容器: $ mkdir -pv /opt/yum/repo $ docker run -it --name yum-sync -v /opt/:/opt/ centos:7 /bin/bash 配置 Yum 的 HTTP 和 HTTPS 代理(原因你懂的): $ vi ~/set_proxy.sh #!/bin/bash export http_proxy=192.168.195.1:1080; export https_proxy=$http_proxy $ chmod a+x ~/set_proxy.sh $ . ~/set_proxy.sh 进入 Base 镜像 Dockerfile 目录: $ cd /opt/kolla/docker/base 配置默认 Yum ,保留缓存: $ cp -v yum.conf /etc/yum.conf $ vi /etc/yum.conf [main] keepcache=1 cachedir=/var/yum/$basearch/$releasever logfile=/var/log/yum.log exactarch=1 obsoletes=1 gpgcheck=1 plugins=1 installonly_limit=0 skip_missing_names_on_install=False 配置 Yun 源和 Key : $ cp -v elasticsearch.repo grafana.repo influxdb.repo kibana.yum.repo MariaDB.repo opendaylight.repo td.repo zookeeper.repo /etc/yum.repos.d/ $ yum -y install http://repo.percona.com/release/7/RPMS/x86_64/percona-release-0.1-4.noarch.rpm $ yum -y install epel-release yum-plugin-priorities centos-release-ceph-jewel centos-release-openstack-pike centos-release-opstools centos-release-qemu-ev $ rpm --import http://yum.mariadb.org/RPM-GPG-KEY-MariaDB \ && rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-Percona \ && rpm --import https://packages.elastic.co/GPG-KEY-elasticsearch \ && rpm --import https://repos.influxdata.com/influxdb.key \ && rpm --import https://packagecloud.io/gpg.key \ && rpm --import https://grafanarel.s3.amazonaws.com/RPM-GPG-KEY-grafana \ && rpm --import https://packages.treasuredata.com/GPG-KEY-td-agent \ && rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 \ && rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-OpsTools \ && rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-Storage \ && rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-Virtualization 获取国内 CentOS 镜像源配置文件: $ yum install -y wget curl git $ cd /etc/yum.repos.d/ $ wget -O CentOS-Base.repo https://lug.ustc.edu.cn/wiki/_export/code/mirrors/help/centos?codeblock=3 创建 Yum 元数据缓存: $ yum makecache 安装创建 Yum 仓库的工具: $ yum install -y createrepo 生成 Yum 仓库同步 Docker 镜像,并删除容器: $ docker commit -m "openstack pike base yum sync." -a "LastRitter" yum-sync yum-sync:pike $ docker rm yum-sync 导出 Yum 仓库同步 Docker 镜像(可选): $ docker save -o openstack_pike_yum_sync_`date +%Y-%m-%d`.tar.gz yum-sync:pike 导入 Yum 仓库同步 Docker 镜像(可选): $ docker load --input openstack_pike_yum_sync_`date +%Y-%m-%d`.tar.gz
同步远程仓库 启动同步镜像,并初设置代理服务器: $ docker run --rm -it -v /opt:/opt/ yum-sync:pike /bin/bash $ . ~/set_proxy.sh 同步所有远程仓库: $ reposync -p /opt/yum/repo/ 单独同步远程仓库(可以启动多个容器同时同步不同仓库): $ reposync -p /opt/yum/repo/ --repoid=base $ reposync -p /opt/yum/repo/ --repoid=updates $ reposync -p /opt/yum/repo/ --repoid=extras $ reposync -p /opt/yum/repo/ --repoid=epel $ reposync -p /opt/yum/repo/ --repoid=centos-ceph-jewel $ reposync -p /opt/yum/repo/ --repoid=centos-openstack-pike $ reposync -p /opt/yum/repo/ --repoid=centos-opstools-release $ reposync -p /opt/yum/repo/ --repoid=centos-qemu-ev $ reposync -p /opt/yum/repo/ --repoid=elasticsearch-2.x $ reposync -p /opt/yum/repo/ --repoid=grafana $ reposync -p /opt/yum/repo/ --repoid=influxdb $ reposync -p /opt/yum/repo/ --repoid=kibana-4.6 $ reposync -p /opt/yum/repo/ --repoid=mariadb $ reposync -p /opt/yum/repo/ --repoid=opendaylight $ reposync -p /opt/yum/repo/ --repoid=percona-release-x86_64 $ reposync -p /opt/yum/repo/ --repoid=percona-release-noarch $ reposync -p /opt/yum/repo/ --repoid=treasuredata $ reposync -p /opt/yum/repo/ --repoid=iwienand-zookeeper-el7 创建本地 Yum 仓库软件包索引文件: $ ls /opt/yum/repo/ | xargs -I {} createrepo -p /opt/yum/repo/{} 备份已同步的软件包(可选): $ cd /opt/yum/repo/ $ ls | xargs -I {} tar cJvf /path/to/backup/yum_repo_{}_`date +%Y-%m-%d`.tar.xz {}
备份仓库配置 启动同步镜像,并进入 Kolla 的 Base 镜像源码目录: $ docker run --rm -it -v /opt:/opt/ yum-sync:pike /bin/bash $ mkdir -pv /opt/kolla/kolla/docker/base/cache $ cd /opt/kolla/kolla/docker/base/cache 保存 Repo 文件: $ mkdir repo $ cp -v /etc/yum.repos.d/* repo/ 保存 RPM 文件: $ mkdir rpms $ cd rpms $ wget http://repo.percona.com/release/7/RPMS/x86_64/percona-release-0.1-4.noarch.rpm $ cp -v /var/yum/x86_64/7/{extras/packages/{epel-release-*,centos-release-*}.rpm,base/packages/yum-plugin-priorities-*.rpm} . $ cd .. 保存 Key 文件: $ mkdirkeys $ cd keys $ wget http://yum.mariadb.org/RPM-GPG-KEY-MariaDB \ && wget https://packages.elastic.co/GPG-KEY-elasticsearch \ && wget https://repos.influxdata.com/influxdb.key \ && wget https://packagecloud.io/gpg.key \ && wget https://grafanarel.s3.amazonaws.com/RPM-GPG-KEY-grafana \ && wget https://packages.treasuredata.com/GPG-KEY-td-agent $ cd ..
启动本地仓库 启动一个 Nginx 服务,暴露 10022 端口,提供 Yum 仓库服务: $ docker run --name=yum-server --restart=always -d -p 10022:80 -v /opt/yum/repo:/usr/share/nginx/html nginx 测试 Web 库服务是否正常: $ curl -X GET http://192.168.195.131:10022/base/repodata/repomd.xml 1522137310 c1561546c684bd06b3a499c2babc35c761b37b2fc331677eca12f0c769b1bb37 99513068b73d614e3d76f22b892fe62bee6af26ed5640d70cb3744e8c57045b5 1522137364 6936336 97041754 1ce4baf2de7b0c88dced853cf47e70788cc69dc1db6b8f4be5d0d04b8690a488 7ab3d5121dd6c296665850a210f1258c857ddc20cdfa8990cab9ccf34acc12f8 1522137364 2831814 26353155 befe5add1fa3a44783fccf25fef6a787a81bcbdca4f19417cfe16e66c5e7f26b 938764645340e4863b503902c10ca326610c430c5e606c5a99461e890713e131 1522137381 10 6025221 29564928 cf0cc856d46b3095106da78256fb28f9d8defea4118d0e75eab07dc53b7d3f0d dbb8218b01cc5d8159c7996cf2aa574aa881d837713f8fae06849b13d14d78a1 1522137367 10 2579184 18237440 a0af68e1057f6b03a36894d3a4f267bbe0590327423d0005d95566fb58cd7a29 967f79ee76ebc7bfe82d74e5aa20403751454f93a5d51ed26f3118e6fda29425 1522137364 1564207 19593459 6cd606547d4f569538d4090e9accdc3c69964de1116b9ab1e0a7864bb1f3ec98 8135f93597ef335a32817b598b45d9f48a1f10271d0ae4263c2860092aab8cba 1522137376 10 7019993 45116416
使用本地仓库 复制之前保存的 Repo 配置文件: $ cd /opt/kolla/kolla/docker/base/cache $ cp -rv repo local 修改软件仓库基地址为 yum_local_repo_url_base ,在使用时替换成真实地址: --- a/docker/base/cache/local/CentOS-Base.repo +++ b/docker/base/cache/local/CentOS-Base.repo @@ -13,7 +13,8 @@ [base] name=CentOS-$releasever - Base #mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=os -baseurl=http://mirrors.ustc.edu.cn/centos/$releasever/os/$basearch/ +#baseurl=http://mirrors.ustc.edu.cn/centos/$releasever/os/$basearch/ +baseurl=http://yum_local_repo_url_base/base/ gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 @@ -21,7 +22,8 @@ gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 [updates] name=CentOS-$releasever - Updates # mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=updates -baseurl=http://mirrors.ustc.edu.cn/centos/$releasever/updates/$basearch/ +#baseurl=http://mirrors.ustc.edu.cn/centos/$releasever/updates/$basearch/ +baseurl=http://yum_local_repo_url_base/updates/ gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 @@ -29,7 +31,8 @@ gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 [extras] name=CentOS-$releasever - Extras # mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=extras -baseurl=http://mirrors.ustc.edu.cn/centos/$releasever/extras/$basearch/ +#baseurl=http://mirrors.ustc.edu.cn/centos/$releasever/extras/$basearch/ +baseurl=http://yum_local_repo_url_base/extras/ gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 @@ -40,4 +43,4 @@ name=CentOS-$releasever - Plus baseurl=http://mirrors.ustc.edu.cn/centos/$releasever/centosplus/$basearch/ gpgcheck=1 enabled=0 -gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 \ No newline at end of file +gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 --- a/docker/base/cache/local/CentOS-Ceph-Jewel.repo +++ b/docker/base/cache/local/CentOS-Ceph-Jewel.repo @@ -5,7 +5,8 @@ [centos-ceph-jewel] name=CentOS-$releasever - Ceph Jewel -baseurl=http://mirror.centos.org/centos/$releasever/storage/$basearch/ceph-jewel/ +#baseurl=http://mirror.centos.org/centos/$releasever/storage/$basearch/ceph-jewel/ +baseurl=http://yum_local_repo_url_base/centos-ceph-jewel/ gpgcheck=1 enabled=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-Storage --- a/docker/base/cache/local/CentOS-OpenStack-pike.repo +++ b/docker/base/cache/local/CentOS-OpenStack-pike.repo @@ -5,7 +5,8 @@ [centos-openstack-pike] name=CentOS-7 - OpenStack pike -baseurl=http://mirror.centos.org/centos/7/cloud/$basearch/openstack-pike/ +#baseurl=http://mirror.centos.org/centos/7/cloud/$basearch/openstack-pike/ +baseurl=http://yum_local_repo_url_base/centos-openstack-pike/ gpgcheck=1 enabled=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-Cloud --- a/docker/base/cache/local/CentOS-OpsTools.repo +++ b/docker/base/cache/local/CentOS-OpsTools.repo @@ -11,7 +11,8 @@ enabled=0 [centos-opstools-release] name=CentOS-7 - OpsTools - release -baseurl=http://mirror.centos.org/centos/$releasever/opstools/$basearch/ +#baseurl=http://mirror.centos.org/centos/$releasever/opstools/$basearch/ +baseurl=http://yum_local_repo_url_base/centos-opstools-release/ gpgcheck=1 enabled=1 skip_if_unavailable=1 --- a/docker/base/cache/local/CentOS-QEMU-EV.repo +++ b/docker/base/cache/local/CentOS-QEMU-EV.repo @@ -5,7 +5,8 @@ [centos-qemu-ev] name=CentOS-$releasever - QEMU EV -baseurl=http://mirror.centos.org/centos/$releasever/virt/$basearch/kvm-common/ +#baseurl=http://mirror.centos.org/centos/$releasever/virt/$basearch/kvm-common/ +baseurl=http://yum_local_repo_url_base/centos-qemu-ev/ gpgcheck=1 enabled=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-Virtualization --- a/docker/base/cache/local/MariaDB.repo +++ b/docker/base/cache/local/MariaDB.repo @@ -1,5 +1,6 @@ [mariadb] name = MariaDB -baseurl = https://yum.mariadb.org/10.0/centos7-amd64 +#baseurl = https://yum.mariadb.org/10.0/centos7-amd64 +baseurl=http://yum_local_repo_url_base/mariadb/ gpgkey = https://yum.mariadb.org/RPM-GPG-KEY-MariaDB gpgcheck = 1 --- a/docker/base/cache/local/elasticsearch.repo +++ b/docker/base/cache/local/elasticsearch.repo @@ -1,6 +1,7 @@ [elasticsearch-2.x] name=Elasticsearch repository for 2.x packages -baseurl=https://packages.elastic.co/elasticsearch/2.x/centos +#baseurl=https://packages.elastic.co/elasticsearch/2.x/centos +baseurl=http://yum_local_repo_url_base/elasticsearch-2.x/ gpgcheck=1 gpgkey=https://packages.elastic.co/GPG-KEY-elasticsearch enabled=1 --- a/docker/base/cache/local/epel.repo +++ b/docker/base/cache/local/epel.repo @@ -1,7 +1,8 @@ [epel] name=Extra Packages for Enterprise Linux 7 - $basearch #baseurl=http://download.fedoraproject.org/pub/epel/7/$basearch -metalink=https://mirrors.fedoraproject.org/metalink?repo=epel-7&arch=$basearch +#metalink=https://mirrors.fedoraproject.org/metalink?repo=epel-7&arch=$basearch +baseurl=http://yum_local_repo_url_base/epel/ failovermethod=priority enabled=1 gpgcheck=1 --- a/docker/base/cache/local/grafana.repo +++ b/docker/base/cache/local/grafana.repo @@ -1,7 +1,8 @@ [grafana] name=grafana -baseurl=https://packagecloud.io/grafana/stable/el/7/$basearch -repo_gpgcheck=1 +#baseurl=https://packagecloud.io/grafana/stable/el/7/$basearch +baseurl=http://yum_local_repo_url_base/grafana/ +#repo_gpgcheck=1 enabled=1 gpgcheck=1 gpgkey=https://packagecloud.io/gpg.key https://grafanarel.s3.amazonaws.com/RPM-GPG-KEY-grafana --- a/docker/base/cache/local/influxdb.repo +++ b/docker/base/cache/local/influxdb.repo @@ -1,6 +1,7 @@ [influxdb] name = InfluxDB Repository - RHEL $releasever -baseurl = https://repos.influxdata.com/rhel/$releasever/$basearch/stable +#baseurl = https://repos.influxdata.com/rhel/$releasever/$basearch/stable +baseurl=http://yum_local_repo_url_base/influxdb/ enabled = 1 gpgcheck = 1 gpgkey = https://repos.influxdata.com/influxdb.key --- a/docker/base/cache/local/kibana.yum.repo +++ b/docker/base/cache/local/kibana.yum.repo @@ -1,6 +1,7 @@ [kibana-4.6] name=Kibana repository for 4.6.x packages -baseurl=https://packages.elastic.co/kibana/4.6/centos +#baseurl=https://packages.elastic.co/kibana/4.6/centos +baseurl=http://yum_local_repo_url_base/kibana-4.6/ gpgcheck=1 gpgkey=https://packages.elastic.co/GPG-KEY-elasticsearch enabled=1 --- a/docker/base/cache/local/opendaylight.repo +++ b/docker/base/cache/local/opendaylight.repo @@ -1,5 +1,6 @@ [opendaylight] name=CentOS CBS OpenDaylight Release Repository -baseurl=http://cbs.centos.org/repos/nfv7-opendaylight-6-release/x86_64/os/ +#baseurl=http://cbs.centos.org/repos/nfv7-opendaylight-6-release/x86_64/os/ +baseurl=http://yum_local_repo_url_base/opendaylight/ enabled=1 gpgcheck=0 --- a/docker/base/cache/local/percona-release.repo +++ b/docker/base/cache/local/percona-release.repo @@ -3,14 +3,16 @@ ######################################## [percona-release-$basearch] name = Percona-Release YUM repository - $basearch -baseurl = http://repo.percona.com/release/$releasever/RPMS/$basearch +#baseurl = http://repo.percona.com/release/$releasever/RPMS/$basearch +baseurl=http://yum_local_repo_url_base/percona-release-$basearch/ enabled = 1 gpgcheck = 1 gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Percona [percona-release-noarch] name = Percona-Release YUM repository - noarch -baseurl = http://repo.percona.com/release/$releasever/RPMS/noarch +#baseurl = http://repo.percona.com/release/$releasever/RPMS/noarch +baseurl=http://yum_local_repo_url_base/percona-release-noarch/ enabled = 1 gpgcheck = 1 gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Percona --- a/docker/base/cache/local/td.repo +++ b/docker/base/cache/local/td.repo @@ -1,5 +1,6 @@ [treasuredata] name=TreasureData -baseurl=http://packages.treasuredata.com/2/redhat/\$releasever/\$basearch +#baseurl=http://packages.treasuredata.com/2/redhat/\$releasever/\$basearch +baseurl=http://yum_local_repo_url_base/treasuredata/ gpgcheck=1 gpgkey=https://packages.treasuredata.com/GPG-KEY-td-agent --- a/docker/base/cache/local/zookeeper.repo +++ b/docker/base/cache/local/zookeeper.repo @@ -1,6 +1,7 @@ [iwienand-zookeeper-el7] name=Copr repo for zookeeper-el7 owned by iwienand -baseurl=https://copr-be.cloud.fedoraproject.org/results/iwienand/zookeeper-el7/epel-7-$basearch/ +#baseurl=https://copr-be.cloud.fedoraproject.org/results/iwienand/zookeeper-el7/epel-7-$basearch/ +baseurl=http://yum_local_repo_url_base/iwienand-zookeeper-el7/ type=rpm-md skip_if_unavailable=True gpgcheck=1 创建 Yum 仓库测试镜像: $ docker run -it --name yum-client -v /opt/:/opt/ centos:7 /bin/bash $ mkdir -pv /tmp/rpms && cd /tmp/rpms $ cp -vf /opt/kolla/kolla/docker/base/cache/rpms/*.rpm . $ rpm -ivh *.rpm $ cd - && rm -rfv /tmp/rpms $ mkdir -pv /tmp/keys && cd /tmp/keys $ cp -vf /opt/kolla/kolla/docker/base/cache/keys/{RPM-GPG-KEY-MariaDB,GPG-KEY-elasticsearch,influxdb.key,gpg.key,RPM-GPG-KEY-grafana,GPG-KEY-td-agent} . $ rpm --import /tmp/keys/RPM-GPG-KEY-MariaDB \ && rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-Percona \ && rpm --import /tmp/keys/GPG-KEY-elasticsearch \ && rpm --import /tmp/keys/influxdb.key \ && rpm --import /tmp/keys/gpg.key \ && rpm --import /tmp/keys/RPM-GPG-KEY-grafana \ && rpm --import /tmp/keys/GPG-KEY-td-agent \ && rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 \ && rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-OpsTools \ && rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-Storage \ && rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-Virtualization $ cd - && rm -rfv /tmp/keys $ cd /etc/yum.repos.d/ $ rm -vf *.repo $ cp -vf /opt/kolla/kolla/docker/base/cache/local/*.repo . $ yum clean all && rm -rf /var/cache/yum 生成 Yum 仓库测试 Docker 镜像,并删除容器: $ docker commit -m "openstack pike yum client." -a "LastRitter" yum-client yum-client:pike $ docker rm yum-client 导出 Yum 仓库同步 Docker 镜像(可选): $ docker save -o openstack_pike_yum_client_`date +%Y-%m-%d`.tar.gz yum-client:pike 导入 Yum 仓库同步 Docker 镜像(可选): $ docker load --input openstack_pike_yum_client_`date +%Y-%m-%d`.tar.gz 把 yum_local_repo_url_base 替换为实际使用的地址 192.168.195.131:10022 ,然后测试 Yum 仓库: $ docker run --rm -it yum-client:pike /bin/bash $ ls /etc/yum.repos.d/*.repo | xargs sed -i 's/yum_local_repo_url_base/192.168.195.131:10022/g' $ yum repolist Loaded plugins: fastestmirror, ovl, priorities base | 2.9 kB 00:00:00 centos-ceph-jewel | 2.9 kB 00:00:00 centos-openstack-pike | 2.9 kB 00:00:00 centos-opstools-release | 2.9 kB 00:00:00 centos-qemu-ev | 2.9 kB 00:00:00 elasticsearch-2.x | 2.9 kB 00:00:00 epel | 2.9 kB 00:00:00 extras | 2.9 kB 00:00:00 grafana | 2.9 kB 00:00:00 influxdb | 2.9 kB 00:00:00 iwienand-zookeeper-el7 | 2.9 kB 00:00:00 kibana-4.6 | 2.9 kB 00:00:00 mariadb | 2.9 kB 00:00:00 opendaylight | 2.9 kB 00:00:00 percona-release-noarch | 2.9 kB 00:00:00 percona-release-x86_64 | 2.9 kB 00:00:00 treasuredata | 2.9 kB 00:00:00 updates | 2.9 kB 00:00:00 (1/18): centos-ceph-jewel/primary_db | 62 kB 00:00:00 (2/18): elasticsearch-2.x/primary_db | 9.4 kB 00:00:00 (3/18): centos-opstools-release/primary_db | 155 kB 00:00:00 (4/18): centos-qemu-ev/primary_db | 34 kB 00:00:00 (5/18): grafana/primary_db | 12 kB 00:00:00 (6/18): centos-openstack-pike/primary_db | 933 kB 00:00:00 (7/18): influxdb/primary_db | 29 kB 00:00:00 (8/18): kibana-4.6/primary_db | 42 kB 00:00:00 (9/18): iwienand-zookeeper-el7/primary_db | 2.4 kB 00:00:00 (10/18): extras/primary_db | 184 kB 00:00:00 (11/18): epel/primary_db | 6.2 MB 00:00:00 (12/18): base/primary_db | 5.7 MB 00:00:00 (13/18): mariadb/primary_db | 21 kB 00:00:00 (14/18): percona-release-noarch/primary_db | 15 kB 00:00:00 (15/18): opendaylight/primary_db | 2.4 kB 00:00:00 (16/18): percona-release-x86_64/x86_64/primary_db | 40 kB 00:00:00 (17/18): treasuredata/primary_db | 47 kB 00:00:00 (18/18): updates/primary_db | 6.9 MB 00:00:00 Determining fastest mirrors repo id repo name status base CentOS-7 - Base 9591 centos-ceph-jewel CentOS-7 - Ceph Jewel 92 centos-openstack-pike CentOS-7 - OpenStack pike 2389 centos-opstools-release CentOS-7 - OpsTools - release 427 centos-qemu-ev CentOS-7 - QEMU EV 47 elasticsearch-2.x Elasticsearch repository for 2.x packages 22 epel Extra Packages for Enterprise Linux 7 - x86_64 12439 extras CentOS-7 - Extras 444 grafana grafana 33 influxdb InfluxDB Repository - RHEL 7 104 iwienand-zookeeper-el7 Copr repo for zookeeper-el7 owned by iwienand 1 kibana-4.6 Kibana repository for 4.6.x packages 14 mariadb MariaDB 15 opendaylight CentOS CBS OpenDaylight Release Repository 1 percona-release-noarch Percona-Release YUM repository - noarch 26 percona-release-x86_64/x86_64 Percona-Release YUM repository - x86_64 70 treasuredata TreasureData 15 updates CentOS-7 - Updates 2411 repolist: 28141
创建本地Pip仓库
初始化Pip容器 创建 pip-server 容器: $ mkdir -pv /opt/pip $ docker run -it --name pip-server -v /opt/:/opt/ centos:7 /bin/bash 安装基本的软件包: $ yum install -y epel-release $ yum install -y python-pip httpd-tools $ pip install --upgrade pip $ pip install pypiserver pip2pi passlib
启动Pip服务 设置密码,并启动 pipyi-server 服务器: $ htpasswd -sc ~/.htaccess admin New password: 123456 Re-type new password: 123456 Adding password for user admin $ pypi-server -p 3141 -P ~/.htaccess /opt/pip 在 pip-server 容器中另外开启一个Shell,测试服务是否正常: $ docker exec -it pip-server bash $ curl -X GET http://localhost:3141 Welcome to pypiserver!

Welcome to pypiserver!

This is a PyPI compatible package index serving 473 packages.

To use this server with pip, run the the following command:

 pip install --extra-index-url http://localhost:3141/ PACKAGE [PACKAGE2...] 

To use this server with easy_install, run the the following command:

 easy_install -i http://localhost:3141/simple/ PACKAGE 

The complete list of all packages can be found here or via the simple index.

This instance is running version 1.2.1 of the pypiserver software.


备份Pip镜像 生成 pip-server 镜像,并删除容器: $ docker commit -m "openstack pike pip server." -a "LastRitter" pip-server pip-server:pike $ docker rm pip-server 导出 pip-server 镜像(可选): $ docker save -o openstack_pike_pip_server_`date +%Y-%m-%d`.tar.gz pip-server:pike 导入 pip-server 镜像(可选): $ docker load --input openstack_pike_pip_server_`date +%Y-%m-%d`.tar.gz
测试Pip镜像 启动一个 pip-server 容器: $ docker run --name=pip-server --restart=always -d -p 3141:3141 -v /opt/:/opt/ pip-server:pike pypi-server -p 3141 -P ~/.htaccess /opt/pip 在宿主机上测试 pip-server 是否正常: $ curl -X GET http://192.168.195.131:3141 Welcome to pypiserver!

Welcome to pypiserver!

This is a PyPI compatible package index serving 996 packages.

To use this server with pip, run the the following command:

 pip install --extra-index-url http://192.168.195.131:3141/ PACKAGE [PACKAGE2...] 

To use this server with easy_install, run the the following command:

 easy_install -i http://192.168.195.131:3141/simple/ PACKAGE 

The complete list of all packages can be found here or via the simple index.

This instance is running version 1.2.1 of the pypiserver software.


下载软件包 单独下载某个软件包: $ docker exec -it pip-server bash $ cd /opt/pip/ && pip download "tox==2.9.1" 批量下载软件包(具体要下载哪些包,可根据构建镜像的 Dockerfile.j2 来确定): $ docker exec -it pip-server bash $ vi /opt/requirements.txt pytest===3.1.3 tox===2.9.1 $ cd /opt/pip/ && pip download -r /opt/requirements.txt 建立索引: $ docker exec -it pip-server dir2pi --normalize-package-names /opt/pip/ # Or $ docker run --rm -it -v /opt/:/opt/ pip-server:pike dir2pi --normalize-package-names /opt/pip/
使用Pip仓库 设置宿主机信任 pip-server : $ mkdir -pv ~/.pip/ $ vi ~/.pip/pip.conf [global] trusted-host = 192.168.195.131 index-url = http://192.168.195.131:3141/simple 安装一个之前缓存过的软件包: # pip install -i http://192.168.195.131:3141/simple/ tox $ pip install tox Collecting tox Downloading http://192.168.195.131:3141/packages/simple/tox/tox-2.9.1-py2.py3-none-any.whl (73kB) 100% |████████████████████████████████| 81kB 49.7MB/s Requirement already satisfied: virtualenv>=1.11.2; python_version != "3.2" in /usr/lib/python2.7/site-packages (from tox) Requirement already satisfied: pluggy<1.0,>=0.3.0 in /usr/lib/python2.7/site-packages (from tox) Requirement already satisfied: six in /usr/lib/python2.7/site-packages (from tox) Requirement already satisfied: py>=1.4.17 in /usr/lib/python2.7/site-packages (from tox) Installing collected packages: tox Successfully installed tox-2.9.1 $ docker logs pip-server 172.17.0.1 - - [31/Mar/2018 15:47:35] "GET / HTTP/1.1" 200 796 192.168.195.131 - - [31/Mar/2018 15:51:27] "GET / HTTP/1.1" 200 808 192.168.195.131 - - [31/Mar/2018 15:57:25] "GET /simple/tox/ HTTP/1.1" 200 464 192.168.195.131 - - [31/Mar/2018 15:57:25] "GET /packages/simple/tox/tox-2.9.1-py2.py3-none-any.whl HTTP/1.1" 200 73454
创建本地Git仓库
配置MySQL服务 启动 mysql-server 容器,设置密码为 123456 : $ docker run -d --name mysql-server -p 13306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql 进入 mysql-server 容器,并安装基本软件包: $ docker exec -it mysql-server bash $ apt-get update $ apt-get install vim 配置 mysql-server 容器,增加 UTF-8 支持: $ vi /etc/mysql/mysql.conf.d/mysqld.cnf [mysqld] pid-file = /var/run/mysqld/mysqld.pid socket = /var/run/mysqld/mysqld.sock datadir = /var/lib/mysql character-set-server = utf8 init_connect = 'SET NAMES utf8'
备份MySQL镜像 生成 mysql-server 镜像,并删除容器: $ docker commit -m "mysql server." -a "LastRitter" mysql-server mysql-server $ docker rm mysql-server 导出 mysql-server 镜像(可选): $ docker save -o openstack_pike_mysql_server_`date +%Y-%m-%d`.tar.gz mysql-server 导入 mysql-server 镜像(可选): $ docker load --input openstack_pike_mysql_server_`date +%Y-%m-%d`.tar.gz
启动MySQL服务 启动 mysql-server 容器: $ mkdir -pv /opt/mysql $ docker run -d --name mysql-server --restart=always -p 13306:3306 -e MYSQL_ROOT_PASSWORD=123456 -v /opt/mysql:/var/lib/mysql -v /etc/localtime:/etc/localtime mysql-server 在主机上测试 mysql-server 服务是否正常: $ yum install -y mysql $ mysql -h 192.168.195.131 -P 13306 -uroot -p123456 Welcome to the MariaDB monitor. Commands end with ; or \g. Your MySQL connection id is 3 Server version: 5.7.21 MySQL Community Server (GPL) Copyright (c) 2000, 2017, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MySQL [(none)]> exit Bye
创建Gogs数据库
进入 mysql-server 容器,创建 Gogs 数据库: $ docker exec -it mysql-server bash $ mysql -h 127.0.0.1 -uroot -p123456 mysql: [Warning] Using a password on the command line interface can be insecure. Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 4 Server version: 5.7.21 MySQL Community Server (GPL) Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> create database gogs default character set utf8 collate utf8_general_ci; Query OK, 1 row affected (0.00 sec) mysql> exit
启动Gogs服务 启动 gogs-server 容器: $ mkdir -pv /opt/gogs $ docker run -d --name=gogs-server --restart=always -p 15555:22 -p 13000:3000 -v /opt/gogs:/data -v /etc/localtime:/etc/localtime gogs/gogs 登录 Gogs 服务 Web 页面 http://192.168.195.131:13000 ,进行相关初始化设置,创建管理员账户,最后设置 SSH Key : 数据库主机:192.168.195.131:13306 数据库用户名:root 数据库用户密码:123456 域名:192.168.195.131 SSH端口:15555 HTTP端口:13000 应用URL:http://192.168.195.131:13000/
同步Nova源码 克隆官方源码: $ git clone https://git.openstack.org/openstack/nova # Or $ git clone https://github.com/openstack/nova.git 在 Gogs 中创建 Nova 项目,然后将其添加为远程仓库: $ cd nova $ git remote add local ssh://git@192.168.195.131:15555/lastritter/nova.git $ git remote -v local ssh://git@192.168.195.131:15555/lastritter/nova.git (fetch) local ssh://git@192.168.195.131:15555/lastritter/nova.git (push) origin https://github.com/openstack/nova.git (fetch) origin https://github.com/openstack/nova.git (push) 推送 master 和 stable/pike 分支到本地 Gogs 仓库: $ git push local master:master $ git checkout -b pike/origin remotes/origin/stable/pike $ git push local pike/origin:pike/origin 查看 16.1.0 版本 Nova 源码的 Tag 信息: $ git show 16.1.0 tag 16.1.0 Tagger: OpenStack Release Bot Date: Thu Feb 15 23:53:11 2018 +0000 nova 16.1.0 release meta:version: 16.1.0 meta:diff-start: - meta:series: pike meta:release-type: release meta:pypi: no meta:first: no meta:release:Author: Matt Riedemann meta:release:Commit: Matt Riedemann meta:release:Change-Id: I0c4d2dfc306d711b1f649d94782e8ae40475c43f meta:release:Code-Review+1: Lee Yarwood meta:release:Code-Review+2: Sean McGinnis meta:release:Code-Review+2: Tony Breeds meta:release:Workflow+1: Tony Breeds -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQEcBAABAgAGBQJahh1nAAoJEEIeZHKBH5qBF70H/ibKNK+jHbcJrqc49ZDC8bU5 86539Pa0QwPbrREwFFzGbt9w9I6Grh9gXa4BpsQAHGM+mRi0RBqCY2WdJ2PoKwEx fH3bCUaYvS4JFUZgQGmpidWM4RPmhOZ4wmdkbqy4soBrncmMsBxnlJ/q91DlPWUd KMH4LGInZ0xq3APvYTNP/H8nJttrIQbgy8hgVPrQ+SLw/1hqW9zSkRqHIBGjlcec EvoQD+2CBQ8Cthn7lsB+5h7x+efYgv+3kAwzvBslMLDp6y+x9VEzkhQIDX4xaD7j 9dLv+0p/OevA7gmC54rTs15R00qf2JzaMqaQ2tUg7HER2S33OfEOfbkcIqd3B78= =yqzR -----END PGP SIGNATURE----- commit 806eda3da84d6f9b47c036ff138415458b837536 Merge: 6d06aa4 6d1877b Author: Zuul Date: Tue Feb 13 17:31:57 2018 +0000 Merge "Query all cells for service version in _validate_bdm" into stable/pike 创建 16.1.0 版本 Nova 的开发分支,并推送到本地 Git 仓库: $ git checkout -b pike/devel $ git reset --hard 16.1.0 HEAD 现在位于 806eda3 Merge "Query all cells for service version in _validate_bdm" into stable/pike $ git push -u local pike/devel:pike/devel Total 0 (delta 0), reused 0 (delta 0) To ssh://git@192.168.195.131:15555/lastritter/nova.git * [new branch] pike/devel -> pike/devel 分支 pike/devel 设置为跟踪来自 local 的远程分支 pike/devel。 查看分支状况: $ git branch -av master c683518 Merge "Fix allocation_candidates not to ignore shared RPs" * pike/devel 806eda3 Merge "Query all cells for service version in _validate_bdm" into stable/pike pike/origin 708342f Merge "compute: Cleans up allocations after failed resize" into stable/pike remotes/origin/HEAD -> origin/master remotes/origin/master c683518 Merge "Fix allocation_candidates not to ignore shared RPs" remotes/origin/stable/ocata 781e7b3 Merge "Don't try to delete build request during a reschedule" into stable/ocata remotes/origin/stable/pike 708342f Merge "compute: Cleans up allocations after failed resize" into stable/pike remotes/origin/stable/queens 307382f Use ksa session for cinder microversion check remotes/work/master c683518 Merge "Fix allocation_candidates not to ignore shared RPs" remotes/work/pike/devel 806eda3 Merge "Query all cells for service version in _validate_bdm" into stable/pike remotes/work/pike/origin 708342f Merge "compute: Cleans up allocations after failed resize" into stable/pike
开始构建镜像
修改镜像模板 修改 Base 镜像 Dockerfile 模板文件( docker/base/Dockerfile.j2 ),把 BEGIN REPO ENABLEMENT 和 END REPO ENABLEMENT 之间的部分替换成如下命令: RUN mkdir -pv /tmp/rpms /tmp/keys COPY cache/rpms/* /tmp/rpms/ COPY cache/keys/* /tmp/keys/ COPY cache/local_repo.conf /tmp/local_repo.conf RUN rpm -ivh /tmp/rpms/*.rpm RUN rpm --import /tmp/keys/RPM-GPG-KEY-MariaDB \ && rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-Percona \ && rpm --import /tmp/keys/GPG-KEY-elasticsearch \ && rpm --import /tmp/keys/influxdb.key \ && rpm --import /tmp/keys/gpg.key \ && rpm --import /tmp/keys/RPM-GPG-KEY-grafana \ && rpm --import /tmp/keys/GPG-KEY-td-agent \ && rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 \ && rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-OpsTools \ && rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-Storage \ && rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-Virtualization RUN rm -rfv /tmp/rpms /tmp/keys /etc/yum.repos.d/* COPY cache/local/* /etc/yum.repos.d/ RUN yum_local_repo_url_base=`cat /tmp/local_repo.conf`;ls /etc/yum.repos.d/*.repo | xargs sed -i 's/yum_local_repo_url_base/'$yum_local_repo_url_base'/g' RUN rm -rfv /tmp/local_repo.conf 新增本地Repo地址配置文件: $ vi docker/base/cache/local_repo.conf 192.168.195.131:10022 下载 dumb-init 文件: $ export http_proxy=192.168.195.1:1080; export https_proxy=$http_proxy $ curl -sSL https://github.com/Yelp/dumb-init/releases/download/v1.1.3/dumb-init_1.1.3_amd64 -o /opt/kolla/kolla/docker/base/cache/dumb-init 使用本地 dumb-init 文件: --- a/docker/base/Dockerfile.j2 +++ b/docker/base/Dockerfile.j2 @@ -253,8 +253,8 @@ COPY curlrc /root/.curlrc {% if base_arch == 'x86_64' %} -RUN curl -sSL https://github.com/Yelp/dumb-init/releases/download/v1.1.3/dumb-init_1.1.3_amd64 -o /usr/local/bin/dumb-init \ - && chmod +x /usr/local/bin/dumb-init \ +COPY cache/dumb-init /usr/local/bin/dumb-init +RUN chmod +x /usr/local/bin/dumb-init \ && sed -i 's|#!|#!/usr/local/bin/dumb-init |' /usr/local/bin/kolla_start {% else %} 下载 get-pip.py 和 requirements-stable-pike.tar.gz 文件: $ mkdir -pv /opt/kolla/kolla/docker/openstack-base/cache $ curl https://bootstrap.pypa.io/get-pip.py -o /opt/kolla/kolla/docker/openstack-base/cache/get-pip.py $ curl http://tarballs.openstack.org/requirements/requirements-stable-pike.tar.gz -o /opt/kolla/kolla/docker/openstack-base/cache/requirements-stable-pike.tar.gz 修改对应的 Dockerfile 模板: --- a/docker/openstack-base/Dockerfile.j2 +++ b/docker/openstack-base/Dockerfile.j2 @@ -276,8 +276,8 @@ ENV DEBIAN_FRONTEND noninteractive {{ macros.install_packages(openstack_base_packages | customizable("packages")) }} {% block source_install_python_pip %} -RUN curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py \ - && python get-pip.py \ +COPY cache/get-pip.py get-pip.py +RUN python get-pip.py \ && rm get-pip.py {% endblock %} @@ -393,7 +393,8 @@ RUN python get-pip.py \ ] %} -ADD openstack-base-archive /openstack-base-source +COPY cache/requirements-stable-pike.tar.gz /tmp/requirements-stable-pike.tar.gz +RUN mkdir -pv /openstack-base-source && tar xvf /tmp/requirements-stable-pike.tar.gz -C /openstack-base-source && rm -rfv /tmp/requirements-stable-pike.tar.gz RUN ln -s openstack-base-source/* /requirements \ && mkdir -p /var/lib/kolla \ && {{ macros.install_pip(['virtualenv'], constraints = false)}} \ 使用本地 Pip 服务: --- a/docker/openstack-base/Dockerfile.j2 +++ b/docker/openstack-base/Dockerfile.j2 @@ -280,7 +280,10 @@ ENV DEBIAN_FRONTEND noninteractive #COPY cache/get-pip.py get-pip.py #RUN python get-pip.py \ # && rm get-pip.py -RUN pip install --upgrade pip +RUN mkdir -pv ~/.pip/ +COPY cache/local_repo.conf /tmp/local_repo.conf +COPY cache/pip.conf /root/.pip/pip.conf +RUN pip_ip=`cat /tmp/local_repo.conf | awk -F : '{print $1}'`; pip_port=`cat /tmp/local_repo.conf | awk -F : '{print $2}'`; sed -i 's/pip_local_repo_ip/'$pip_ip'/g' /root/.pip/pip.conf; sed -i 's/pip_local_repo_port/'$pip_port'/g' /root/.pip/pip.conf; pip install --upgrade pip && rm -rfv /tmp/local_repo.conf {% endblock %} {% set openstack_base_pip_packages = [ --- /dev/null +++ b/docker/openstack-base/cache/local_repo.conf @@ -0,0 +1 @@ +192.168.195.131:3141 --- /dev/null +++ b/docker/openstack-base/cache/pip.conf @@ -0,0 +1,3 @@ +[global] +trusted-host = pip_local_repo_ip +index-url = http://pip_local_repo_ip:pip_local_repo_port/simple
基本使用说明
命令帮助查看: $ python tools/build.py --help usage: kolla-build [-h] [--base BASE] [--base-arch BASE_ARCH] [--base-image BASE_IMAGE] [--base-tag BASE_TAG] [--build-args BUILD_ARGS] [--cache] [--config-dir DIR] [--config-file PATH] [--debug] [--docker-dir DOCKER_DIR] [--format FORMAT] [--keep] [--list-dependencies] [--list-images] [--logs-dir LOGS_DIR] [--namespace NAMESPACE] [--nocache] [--nodebug] [--nokeep] [--nolist-dependencies] [--nolist-images] [--nopull] [--nopush] [--noskip-existing] [--noskip-parents] [--notemplate-only] [--profile PROFILE] [--pull] [--push] [--push-threads PUSH_THREADS] [--registry REGISTRY] [--retries RETRIES] [--save-dependency SAVE_DEPENDENCY] [--skip-existing] [--skip-parents] [--tag TAG] [--tarballs-base TARBALLS_BASE] [--template-only] [--template-override TEMPLATE_OVERRIDE] [--threads THREADS] [--timeout TIMEOUT] [--type INSTALL_TYPE] [--version] [--work-dir WORK_DIR] [regex [regex ...]] positional arguments: regex Build only images matching regex and its dependencies optional arguments: -h, --help show this help message and exit --base BASE, -b BASE The distro type of the base image. Allowed values are centos, rhel, ubuntu, oraclelinux, debian Allowed values: centos, rhel, ubuntu, oraclelinux, debian --base-arch BASE_ARCH The base architecture. Default is same as host Allowed values: x86_64, ppc64le, aarch64 --base-image BASE_IMAGE The base image name. Default is the same with base. For non-x86 architectures use full name like "aarch64/debian". --base-tag BASE_TAG The base distro image tag --build-args BUILD_ARGS Set docker build time variables --cache Use the Docker cache when building --config-dir DIR Path to a config directory to pull `*.conf` files from. This file set is sorted, so as to provide a predictable parse order if individual options are over-ridden. The set is parsed after the file(s) specified via previous --config-file, arguments hence over-ridden options in the directory take precedence. --config-file PATH Path to a config file to use. Multiple config files can be specified, with values in later files taking precedence. Defaults to None. --debug, -d Turn on debugging log level --docker-dir DOCKER_DIR, -D DOCKER_DIR Path to additional docker file template directory --format FORMAT, -f FORMAT Format to write the final results in Allowed values: json, none --keep Keep failed intermediate containers --list-dependencies, -l Show image dependencies (filtering supported) --list-images Show all available images (filtering supported) --logs-dir LOGS_DIR Path to logs directory --namespace NAMESPACE, -n NAMESPACE The Docker namespace name --nocache The inverse of --cache --nodebug The inverse of --debug --nokeep The inverse of --keep --nolist-dependencies The inverse of --list-dependencies --nolist-images The inverse of --list-images --nopull The inverse of --pull --nopush The inverse of --push --noskip-existing The inverse of --skip-existing --noskip-parents The inverse of --skip-parents --notemplate-only The inverse of --template-only --profile PROFILE, -p PROFILE Build a pre-defined set of images, see [profiles] section in config. The default profiles are: infra, main, aux, default, gate --pull Attempt to pull a newer version of the base image --push Push images after building --push-threads PUSH_THREADS The number of threads to user while pushing Images. Note: Docker can not handle threading push properly --registry REGISTRY The docker registry host. The default registry host is Docker Hub --retries RETRIES, -r RETRIES The number of times to retry while building --save-dependency SAVE_DEPENDENCY Path to the file to store the docker image dependency in Graphviz dot format --skip-existing Do not rebuild images present in the docker cache --skip-parents Do not rebuild parents of matched images --tag TAG The Docker tag --tarballs-base TARBALLS_BASE Base url to OpenStack tarballs --template-only Don't build images. Generate Dockerfile only --template-override TEMPLATE_OVERRIDE Path to template override file --threads THREADS, -T THREADS The number of threads to use while building. (Note: setting to one will allow real time logging) --timeout TIMEOUT Time in seconds after which any operation times out --type INSTALL_TYPE, -t INSTALL_TYPE The method of the OpenStack install. Allowed values are binary, source, rdo, rhos Allowed values: binary, source, rdo, rhos --version show program's version number and exit --work-dir WORK_DIR Path to be used as working directory.By default, a temporary dir is created --base BASE, -b BASE 指定Base镜像的发行版,默认值是 centos ,可选的值有: centos , rhel , ubuntu , oraclelinux , debian ; --base-arch BASE_ARCH 指定系统架构,默认值与主机相同,可选的值有: x86_64 , ppc64le , aarch64 ; --base-image BASE_IMAGE 指定Base镜像名称,默认与 --base 参数相同,对于非 x86 架构,使用全名,比如 aarch64/debian ; --base-tag BASE_TAG 指定 Base 发行版的镜像 Tag ; --build-args BUILD_ARGS 设置 Docker 构建时的变量; --cache 构建时使用缓存; --config-dir DIR 指定 *.conf 配置文件目录,配置文件按顺序执行,且在 --config-file 指定的文件之后; --config-file PATH 指定要使用的配置文件,可以指定多次; --debug, -d 开启Debug日志; --docker-dir DOCKER_DIR, -D DOCKER_DIR 指定 Dockerfile 临时目录; --format FORMAT, -f FORMAT 指定执行结果格式,可选值有: json , none ; --keep 保留失败的临时容器; --list-dependencies, -l 显示镜像依赖(支持过滤); --list-images 显示所有可用镜像(支持过滤); --logs-dir LOGS_DIR 指定日志目录; --namespace NAMESPACE 指定镜像的名字空间; --nocache 不使用缓存; --nodebug 不显示调试信息; --nokeep 不保留临时容器; --nolist-dependencies 不显示镜像依赖; --nolist-images 不显示可构建镜像; --nopull 不尝试拉取新版Base镜像; --nopush 构建完成后不push镜像; --noskip-existing 构建时不跳过存在的镜像; --noskip-parents 构建时不跳过依赖镜像; --notemplate-only 生成 Dockerfile ,同时构建镜像; --profile PROFILE, -p PROFILE 构建配置文件 [profiles] 节中预定义的镜像,默认值有: infra , main , aux , default , gate ; --pull 构建时试图拉取最新的 Base 镜像; --push 构建完毕后 push 镜像; --push-threads PUSH_THREADS 指定 push 时的线程数; --registry REGISTRY 指定 push 的 Docker Registry ,默认是 Docker Hub ; --retries RETRIES, -r RETRIES 指定构建时重试次数; --save-dependency SAVE_DEPENDENCY 保存 Graphviz dot format 格式的镜像依赖关系到指定路径。 --skip-existing 不构建 Docker 缓存中存在的镜像; --skip-parents 不构建依赖的镜像; --tag TAG 指定生成的镜像 Tag ; --tarballs-base TARBALLS_BASE 指定 OpenStack 压缩包 Base URL ; --template-only 只生成 Dockerfile ,不构建镜像; --template-override TEMPLATE_OVERRIDE 指定临时覆盖文件; --threads THREADS, -T THREADS 指定构建线程数量; --timeout TIMEOUT 设置操作超时时间,单位是秒; --type INSTALL_TYPE, -t INSTALL_TYPE 设置 OpenStack 安装类型,可选的值有: binary , source , rdo , rhos ; --version 显示程序版本; --work-dir WORK_DIR 指定工作目录,默认使用临时目录。
正式开始构建 使用默认配置,构建所有源码镜像: $ python tools/build.py -t source 使用默认配置,不使用缓存,构建 openstack-base 和所依赖的源码镜像: $ python tools/build.py -t source --nocache openstack-base 使用 profile 构建 openstack-base 镜像: $ cp -v /opt/kolla/kolla/etc/kolla/kolla-build.conf /opt/kolla/ $ vi /opt/kolla/kolla-build.conf [profiles] myprofile=openstack-base $ python tools/build.py -t source --debug --nocache --nopull --work-dir /opt/kolla/ --config-file /opt/kolla/kolla-build.conf --profile myprofile 使用默认配置,不使用缓存,不构建或者 pull 依赖的镜像,构建 nova-base 源码镜像: $ python tools/build.py -t source --nocache --skip-parents --nopull nova-base 使用本地源码构建 nova-base 镜像: $ mkdir -pv docker/nova/nova-base/cache $ wget http://tarballs.openstack.org/nova/nova-16.1.0.tar.gz -O /opt/kolla/kolla/docker/nova/nova-base/cache/nova-16.1.0.tar.gz $ wget http://tarballs.openstack.org/blazar/blazar-0.3.0.tar.gz -O /opt/kolla/kolla/docker/nova/nova-base/cache/blazar-0.3.0.tar.gz $ vi /opt/kolla/kolla-build.conf [nova-base] type = local location = /opt/kolla/kolla/docker/nova/nova-base/cache/nova-16.1.0.tar.gz [nova-base-plugin-blazar] type = local location = /opt/kolla/kolla/docker/nova/nova-base/cache/blazar-0.3.0.tar.gz $ python tools/build.py -t source --nocache --skip-parents --nopull --config-file /opt/kolla/kolla-build.conf nova-base 使用 Git 源码构建镜(不使用 --nocache 参数,也可以正确更新源码): $ vi /opt/kolla/kolla-build.conf [nova-base] type = git location = http://192.168.195.131:13000/lastritter/nova.git reference = pike/devel [nova-base-plugin-blazar] type = local location = /opt/kolla/kolla/docker/nova/nova-base/cache/blazar-0.3.0.tar.gz $ python tools/build.py -t source --skip-parents --nopull --config-file /opt/kolla/kolla-build.conf nova-base
创建构建镜像 启动 Docker In Docker 服务镜像: $ docker run --privileged --name dind --restart=always -d docker:stable-dind $ docker exec -it dind sh $ vi /etc/docker/daemon.json { "registry-mirrors": ["https://7g5a4z30.mirror.aliyuncs.com"] } $ docker restart dind 启动 Docker In Docker 客户端镜像,并安装基本的软件包: $ docker run -it --name kolla-build --link dind:docker docker:edge sh $ apk update $ apk add git py-pip gcc python-dev linux-headers libffi-dev musl-dev openssl-dev perl python sshpass $ pip install --upgrade pip $ pip install pyopenssl tox 获取 Kolla 源码: $ git clone https://gitee.com/lastritter/kolla.git $ cd kolla # git checkout -b devel/pike remotes/origin/devel/pike 生成配置文件: $ pip install -r requirements.txt -r test-requirements.txt $ tox -e genconfig $ mkdir -pv /etc/kolla/ $ cp -v etc/kolla/kolla-build.conf /etc/kolla/ 生成 Kolla 构建 Docker 镜像,并删除容器: $ docker commit -m "openstack pike kolla build." -a "LastRitter" kolla-build kolla-build:pike $ docker rm kolla-build 导出 Kolla 构建 Docker 镜像(可选): $ docker save -o openstack_pike_kolla_build_`date +%Y-%m-%d`.tar.gz kolla-build:pike 导入 Kolla 构建 Docker 镜像(可选): $ docker load --input openstack_pike_kolla_build_`date +%Y-%m-%d`.tar.gz
快速部署环境
使用前面生成的 Docker 镜像,以及修改过的 Kolla 源码,快速部署新的构建环境,也可以只部署 Kolla-Build 镜像,其他的使用原有服务。部署环境的主机 IP 是 172.29.101.166 。
部署Yum服务 导入 Yum 同步镜像: $ mkdir -pv /opt/yum/repo $ docker load --input /path/to/openstack_pike_yum_sync_2018-03-27.tar.gz 同步远程 Yum 源: $ docker run --rm -it -v /opt:/opt/ yum-sync:pike reposync -p /opt/yum/repo/ # Or $ docker run --rm -it -v /opt:/opt/ yum-sync:pike reposync -p /opt/yum/repo/ --repoid=base 创建本地 Yum 仓库软件包索引文件: $ yum install -y createrepo $ ls /opt/yum/repo/ | xargs -I {} createrepo -p /opt/yum/repo/{} 启动 Yum 仓库服务,暴露 12222 端口: $ docker run --name=yum-server --restart=always -d -p 12222:80 -v /opt/yum/repo:/usr/share/nginx/html nginx 测试 Yum 仓库服务是否正常: $ curl -X GET http://172.29.101.166:12222/base/repodata/repomd.xml 导入 Yum 仓库测试 Docker 镜像: $ docker load --input /path/to/openstack_pike_yum_client_2018-03-30.tar.gz 测试 Yum 仓库是否正常: $ docker run --rm -it yum-client:pike /bin/bash $ ls /etc/yum.repos.d/*.repo | xargs sed -i 's/yum_local_repo_url_base/172.29.101.166:12222/g' $ yum repolist
部署Pip服务 导入 pip-server 镜: $ docker load --input openstack_pike_pip_server_2018-03-31.tar.gz 启动一个 pip-server 容器: $ mkdir -pv /opt/pip $ docker run --name=pip-server --restart=always -d -p 3141:3141 -v /opt/:/opt/ pip-server:pike pypi-server -p 3141 -P ~/.htaccess /opt/pip 批量下载软件包: $ vi /opt/requirements.txt $ docker run --rm -it -v /opt/:/opt/ pip-server:pike bash $ cd /opt/pip/ && pip download -r /opt/requirements.txt 建立索引: $ docker run --rm -it -v /opt/:/opt/ pip-server:pike dir2pi --normalize-package-names /opt/pip/
部署Kolla环境 导入 Kolla-Build 镜像 $ docker load --input openstack_pike_kolla_build_2018-03-30.tar.gz 启动 Kolla-Build 镜像: $ docker run --privileged --name dind --restart=always -d docker:stable-dind $ docker exec -it dind sh $ vi /etc/docker/daemon.json { "registry-mirrors": ["https://7g5a4z30.mirror.aliyuncs.com"] } $ docker restart dind $ docker run -it --name kolla-build --link dind:docker kolla-build:pike sh 替换本地 Yum 和 Pip 服务的 IP 地址: $ cd kolla && git pull $ vi docker/base/cache/local_repo.conf 172.29.101.166:12222 $ vi docker/openstack-basebase/cache/local_repo.conf 172.29.101.166:3141
正式开始构建 构建 openstack-base 镜像: $ docker pull centos:7 $ python tools/build.py -t source --nocache openstack-base # Or $ python tools/build.py -t source --skip-parents openstack-base 查看构建成功的镜像: $ docker run -it --rm --link dind:docker kolla-build:pike docker images
镜像启动分析
为了方便理解,使用如下命令生成的 Dockerfile 来进行分析: $ python tools/build.py -t source --template-only --work-dir=.. 查看 Base 镜像的 Dockerfile ,容器启动时,使用 dumb-init 程序来创建初始化进程环境,执行 kolla_start (来源于 start.sh 脚本)命令( ../docker/base/Dockerfile ): COPY set_configs.py /usr/local/bin/kolla_set_configs COPY start.sh /usr/local/bin/kolla_start COPY sudoers /etc/sudoers COPY curlrc /root/.curlrc COPY cache/dumb-init /usr/local/bin/dumb-init RUN chmod +x /usr/local/bin/dumb-init \ && sed -i 's|#!|#!/usr/local/bin/dumb-init |' /usr/local/bin/kolla_start RUN touch /usr/local/bin/kolla_extend_start \ && chmod 755 /usr/local/bin/kolla_start /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_set_configs \ && chmod 440 /etc/sudoers \ && mkdir -p /var/log/kolla \ && chown :kolla /var/log/kolla \ && chmod 2775 /var/log/kolla \ && rm -f /tmp/kolla_bashrc CMD ["kolla_start"] start.sh 脚本首先执行 kolla_set_configs (由 docker/base/set_configs.py 复制生成)来初始化配置,然后执行扩展启动命令 kolla_extend_start (由派生的镜像提供),最后执行 /run_command 中指定的命令启动服务( docker/base/start.sh ): #!/bin/bash set -o errexit # Processing /var/lib/kolla/config_files/config.json as root. This is necessary # to permit certain files to be controlled by the root user which should # not be writable by the dropped-privileged user, especially /run_command sudo -E kolla_set_configs CMD=$(cat /run_command) ARGS="" if [[ ! "${!KOLLA_SKIP_EXTEND_START[@]}" ]]; then # Run additional commands if present . kolla_extend_start fi echo "Running command: '${CMD}${ARGS:+ $ARGS}'" exec ${CMD} ${ARGS} You have new mail. kolla_set_configs 主函数(docker/base/start.sh): def main(): try: parser = argparse.ArgumentParser() parser.add_argument('--check', action='store_true', required=False, help='Check whether the configs changed') args = parser.parse_args() config = load_config() if args.check: execute_config_check(config) else: execute_config_strategy(config) except ExitingException as e: LOG.error("%s: %s", e.__class__.__name__, e) return e.exit_code except Exception: LOG.exception('Unexpected error:') return 2 return 0 if __name__ == "__main__": sys.exit(main()) kolla_set_configs 首先加载配置文件,如果没有定义 KOLLA_CONFIG 环境变量,则加载 /var/lib/kolla/config_files/config.json 这个 JSon 配置文件( docker/base/start.sh ): def load_config(): def load_from_env(): config_raw = os.environ.get("KOLLA_CONFIG") if config_raw is None: return None # Attempt to read config try: return json.loads(config_raw) except ValueError: raise InvalidConfig('Invalid json for Kolla config') def load_from_file(): config_file = os.environ.get("KOLLA_CONFIG_FILE") if not config_file: config_file = '/var/lib/kolla/config_files/config.json' LOG.info("Loading config file at %s", config_file) # Attempt to read config file with open(config_file) as f: try: return json.load(f) except ValueError: raise InvalidConfig( "Invalid json file found at %s" % config_file) except IOError as e: raise InvalidConfig( "Could not read file %s: %r" % (config_file, e)) config = load_from_env() if config is None: config = load_from_file() LOG.info('Validating config file') validate_config(config) return config kolla_set_configs 最后根据配置文件和环境变量复制 OpenStack 组件的配置文件,并把启动命令写入 /run_command 文件( docker/base/start.sh ): def execute_config_strategy(config): config_strategy = os.environ.get("KOLLA_CONFIG_STRATEGY") LOG.info("Kolla config strategy set to: %s", config_strategy) if config_strategy == "COPY_ALWAYS": copy_config(config) handle_permissions(config) elif config_strategy == "COPY_ONCE": if os.path.exists('/configured'): raise ImmutableConfig( "The config strategy prevents copying new configs", exit_code=0) else: copy_config(config) handle_permissions(config) os.mknod('/configured') else: raise InvalidConfig('KOLLA_CONFIG_STRATEGY is not set properly') def copy_config(config): if 'config_files' in config: LOG.info('Copying service configuration files') for data in config['config_files']: config_file = ConfigFile(**data) config_file.copy() else: LOG.debug('No files to copy found in config') LOG.info('Writing out command to execute') LOG.debug("Command is: %s", config['command']) # The value from the 'command' key will be written to '/run_command' with open('/run_command', 'w+') as f: f.write(config['command']) 查看 nova_compute 容器中的环境变量和配置: $ docker exec -it nova_compute bash $ env | grep KOLLA KOLLA_CONFIG_STRATEGY=COPY_ALWAYS KOLLA_BASE_DISTRO=centos KOLLA_INSTALL_TYPE=source PS1=$(tput bold)($(printenv KOLLA_SERVICE_NAME))$(tput sgr0)[$(id -un)@$(hostname -s) $(pwd)]$ KOLLA_SERVICE_NAME=nova-compute KOLLA_INSTALL_METATYPE=mixed $ cat /run_command nova-compute $ cat /var/lib/kolla/config_files/config.json { "command": "nova-compute", "config_files": [ { "source": "/var/lib/kolla/config_files/nova.conf", "dest": "/etc/nova/nova.conf", "owner": "nova", "perm": "0600" }, { "source": "/var/lib/kolla/config_files/policy.json", "dest": "/etc/nova/policy.json", "owner": "nova", "perm": "0600", "optional": true } ], "permissions": [ { "path": "/var/log/kolla/nova", "owner": "nova:nova", "recurse": true }, { "path": "/var/lib/nova", "owner": "nova:nova", "recurse": true } ] }
Kolla源码分析
命令总览 查看 build.py 命令,是一个符号链接: $ ll tools/build.py lrwxrwxrwx 1 root root 21 3月 20 13:30 tools/build.py -> ../kolla/cmd/build.py 命令入口,调用 kolla.image.build 模块的 run_build 方法(kolla/cmd/build.py): import os import sys # NOTE(SamYaple): Update the search path to prefer PROJECT_ROOT as the source # of packages to import if we are using local tools instead of # pip installed kolla tools PROJECT_ROOT = os.path.abspath(os.path.join( os.path.dirname(os.path.realpath(__file__)), '../..')) if PROJECT_ROOT not in sys.path: sys.path.insert(0, PROJECT_ROOT) from kolla.image import build def main(): statuses = build.run_build() if statuses: (bad_results, good_results, unmatched_results, skipped_results) = statuses if bad_results: return 1 return 0 if __name__ == '__main__': sys.exit(main()) 命令主函数(kolla/image/build.py): def run_build(): """Build container images. :return: A 3-tuple containing bad, good, and unmatched container image status dicts, or None if no images were built. """ conf = cfg.ConfigOpts() common_config.parse(conf, sys.argv[1:], prog='kolla-build') if conf.debug: LOG.setLevel(logging.DEBUG) kolla = KollaWorker(conf) kolla.setup_working_dir() kolla.find_dockerfiles() kolla.create_dockerfiles() if conf.template_only: LOG.info('Dockerfiles are generated in %s', kolla.working_dir) return # We set the atime and mtime to 0 epoch to preserve allow the Docker cache # to work like we want. A different size or hash will still force a rebuild kolla.set_time() if conf.save_dependency: kolla.build_image_list() kolla.find_parents() kolla.filter_images() kolla.save_dependency(conf.save_dependency) LOG.info('Docker images dependency are saved in %s', conf.save_dependency) return if conf.list_images: kolla.build_image_list() kolla.find_parents() kolla.filter_images() kolla.list_images() return if conf.list_dependencies: kolla.build_image_list() kolla.find_parents() kolla.filter_images() kolla.list_dependencies() return push_queue = six.moves.queue.Queue() queue = kolla.build_queue(push_queue) workers = [] with join_many(workers): try: for x in six.moves.range(conf.threads): worker = WorkerThread(conf, queue) worker.setDaemon(True) worker.start() workers.append(worker) for x in six.moves.range(conf.push_threads): worker = WorkerThread(conf, push_queue) worker.setDaemon(True) worker.start() workers.append(worker) # sleep until queue is empty while queue.unfinished_tasks or push_queue.unfinished_tasks: time.sleep(3) # ensure all threads exited happily push_queue.put(WorkerThread.tombstone) queue.put(WorkerThread.tombstone) except KeyboardInterrupt: for w in workers: w.should_stop = True push_queue.put(WorkerThread.tombstone) queue.put(WorkerThread.tombstone) raise results = kolla.summary() kolla.cleanup() if conf.format == 'json': print(json.dumps(results)) return kolla.get_image_statuses()
参数解析 解析配置文件和命令行参数(kolla/image/build.py): from oslo_config import cfg #... def run_build(): #... conf = cfg.ConfigOpts() common_config.parse(conf, sys.argv[1:], prog='kolla-build') #... 参数解析(kolla/common/config.py): def parse(conf, args, usage=None, prog=None, default_config_files=None): conf.register_cli_opts(_CLI_OPTS) conf.register_opts(_BASE_OPTS) conf.register_opts(_PROFILE_OPTS, group='profiles') for name, opts in gen_all_source_opts(): conf.register_opts(opts, name) for name, opts in gen_all_user_opts(): conf.register_opts(opts, name) conf(args=args, project='kolla', usage=usage, prog=prog, version=version.cached_version_string(), default_config_files=default_config_files) # NOTE(jeffrey4l): set the default base tag based on the # base option conf.set_default('base_tag', DEFAULT_BASE_TAGS.get(conf.base)) if not conf.base_image: conf.base_image = conf.base 查看默认和可选参数配置(kolla/common/config.py): #... BASE_OS_DISTRO = ['centos', 'rhel', 'ubuntu', 'oraclelinux', 'debian'] BASE_ARCH = ['x86_64', 'ppc64le', 'aarch64'] DEFAULT_BASE_TAGS = { 'centos': '7', 'rhel': '7', 'oraclelinux': '7-slim', 'debian': 'stretch', 'ubuntu': '16.04', } DISTRO_RELEASE = { 'centos': '7', 'rhel': '7', 'oraclelinux': '7', 'debian': 'stretch', 'ubuntu': '16.04', } # This is noarch repository so we will use it on all architectures DELOREAN = \ "https://trunk.rdoproject.org/centos7/current-passed-ci/delorean.repo" # TODO(hrw): with move to Pike+1 we need to make sure that aarch64 repo # gets updated (docker/base/aarch64-cbs.repo file) # there is ongoing work to sort that out DELOREAN_DEPS = { 'x86_64': "https://trunk.rdoproject.org/centos7/delorean-deps.repo", 'aarch64': "", 'ppc64le': "" } INSTALL_TYPE_CHOICES = ['binary', 'source', 'rdo', 'rhos'] TARBALLS_BASE = "http://tarballs.openstack.org" #... SOURCES = { 'openstack-base': { 'type': 'url', 'location': ('$tarballs_base/requirements/' 'requirements-stable-pike.tar.gz')}, 'aodh-base': { 'type': 'url', 'location': ('$tarballs_base/aodh/' 'aodh-5.1.0.tar.gz')}, 'barbican-base': { 'type': 'url', 'location': ('$tarballs_base/barbican/' 'barbican-5.0.0.tar.gz')}, 'bifrost-base': { 'type': 'url', 'location': ('$tarballs_base/bifrost/' 'bifrost-4.0.1.tar.gz')}, #...
准备构建环境 创建 KollaWorker 对象(kolla/image/build.py): #... def run_build(): #... kolla = KollaWorker(conf) #... 初始化构建过程中的各种变量(kolla/image/build.py): class KollaWorker(object): def __init__(self, conf): self.conf = conf self.images_dir = self._get_images_dir() self.registry = conf.registry if self.registry: self.namespace = self.registry + '/' + conf.namespace else: self.namespace = conf.namespace self.base = conf.base self.base_tag = conf.base_tag self.install_type = conf.install_type self.tag = conf.tag self.base_arch = conf.base_arch self.images = list() rpm_setup_config = ([repo_file for repo_file in conf.rpm_setup_config if repo_file is not None]) self.rpm_setup = self.build_rpm_setup(rpm_setup_config) rh_base = ['centos', 'oraclelinux', 'rhel'] rh_type = ['source', 'binary', 'rdo', 'rhos'] deb_base = ['ubuntu', 'debian'] deb_type = ['source', 'binary'] if not ((self.base in rh_base and self.install_type in rh_type) or (self.base in deb_base and self.install_type in deb_type)): raise exception.KollaMismatchBaseTypeException( '{} is unavailable for {}'.format(self.install_type, self.base) ) if self.install_type == 'binary': self.install_metatype = 'rdo' elif self.install_type == 'source': self.install_metatype = 'mixed' elif self.install_type == 'rdo': self.install_type = 'binary' self.install_metatype = 'rdo' elif self.install_type == 'rhos': self.install_type = 'binary' self.install_metatype = 'rhos' else: raise exception.KollaUnknownBuildTypeException( 'Unknown install type' ) self.image_prefix = self.base + '-' + self.install_type + '-' self.regex = conf.regex self.image_statuses_bad = dict() self.image_statuses_good = dict() self.image_statuses_unmatched = dict() self.image_statuses_skipped = dict() self.maintainer = conf.maintainer docker_kwargs = docker.utils.kwargs_from_env() self.dc = docker.APIClient(version='auto', **docker_kwargs)
设置工作目录 使用 KollaWorker 初始化工作目录(kolla/image/build.py): #... def run_build(): #... kolla.setup_working_dir() #... 如果设置了 work_dir 则在指定路径中创建 docker 目录作为工作目录,否则使用时间戳生成一个临时目录,最后把 docker 目录里的所有文件复制到工作目录(kolla/image/build.py): class KollaWorker(object): #... def setup_working_dir(self): """Creates a working directory for use while building.""" if self.conf.work_dir: self.working_dir = os.path.join(self.conf.work_dir, 'docker') else: ts = time.time() ts = datetime.datetime.fromtimestamp(ts).strftime( '%Y-%m-%d_%H-%M-%S_') self.temp_dir = tempfile.mkdtemp(prefix='kolla-' + ts) self.working_dir = os.path.join(self.temp_dir, 'docker') self.copy_dir(self.images_dir, self.working_dir) for dir in self.conf.docker_dir: self.copy_dir(dir, self.working_dir) self.copy_apt_files() LOG.debug('Created working dir: %s', self.working_dir)
查找Dockerfile.j2 使用 KollaWorker 对象查找所有的 Dockerfile.j2 (kolla/image/build.py): #... def run_build(): #... kolla.find_dockerfiles() #... 生成所有 Dockerfile.j2 文件的列表(kolla/image/build.py): class KollaWorker(object): #... def find_dockerfiles(self): """Recursive search for Dockerfiles in the working directory.""" self.docker_build_paths = list() path = self.working_dir filename = 'Dockerfile.j2' for root, dirs, names in os.walk(path): if filename in names: self.docker_build_paths.append(root) LOG.debug('Found %s', root.split(self.working_dir)[1]) LOG.debug('Found %d Dockerfiles', len(self.docker_build_paths))
生成Dockerfile 使用 KollaWorker 对象生成 Dockerfile (kolla/image/build.py): #... def run_build(): #... kolla.create_dockerfiles() #... 首先生成各种变量配置的列表,最后使用 Dockerfile.j2 模板文件生成 Dockerfile (kolla/image/build.py): class KollaWorker(object): #... def create_dockerfiles(self): kolla_version = version.version_info.cached_version_string() supported_distro_release = common_config.DISTRO_RELEASE.get( self.base) for path in self.docker_build_paths: template_name = "Dockerfile.j2" image_name = path.split("/")[-1] ts = time.time() build_date = datetime.datetime.fromtimestamp(ts).strftime( '%Y%m%d') values = {'base_distro': self.base, 'base_image': self.conf.base_image, 'base_distro_tag': self.base_tag, 'base_arch': self.base_arch, 'supported_distro_release': supported_distro_release, 'install_metatype': self.install_metatype, 'image_prefix': self.image_prefix, 'install_type': self.install_type, 'namespace': self.namespace, 'tag': self.tag, 'maintainer': self.maintainer, 'kolla_version': kolla_version, 'image_name': image_name, 'users': self.get_users(), 'rpm_setup': self.rpm_setup, 'build_date': build_date} env = jinja2.Environment( # nosec: not used to render HTML loader=jinja2.FileSystemLoader(self.working_dir)) env.filters.update(self._get_filters()) env.globals.update(self._get_methods()) tpl_path = os.path.join( os.path.relpath(path, self.working_dir), template_name) template = env.get_template(tpl_path) if self.conf.template_override: tpl_dict = self._merge_overrides(self.conf.template_override) template_name = os.path.basename(tpl_dict.keys()[0]) values['parent_template'] = template env = jinja2.Environment( # nosec: not used to render HTML loader=jinja2.DictLoader(tpl_dict)) env.filters.update(self._get_filters()) env.globals.update(self._get_methods()) template = env.get_template(template_name) content = template.render(values) content_path = os.path.join(path, 'Dockerfile') with open(content_path, 'w') as f: LOG.debug("Rendered %s into:", tpl_path) LOG.debug(content) f.write(content) LOG.debug("Wrote it to %s", content_path)
(kolla/image/build.py): from kolla.template import filters as jinja_filters #... class KollaWorker(object): #... def _get_filters(self): filters = { 'customizable': jinja_filters.customizable, } return filters def _get_methods(self): """Mapping of available Jinja methods. return a dictionary that maps available function names and their corresponding python methods to make them available in jinja templates """ return { 'debian_package_install': jinja_methods.debian_package_install, }
(kolla/template/filters.py): from jinja2 import contextfilter @contextfilter def customizable(context, val_list, call_type): name = context['image_name'].replace("-", "_") + "_" + call_type + "_" if name + "override" in context: return context[name + "override"] if name + "append" in context: val_list.extend(context[name + "append"]) if name + "remove" in context: for removal in context[name + "remove"]: if removal in val_list: val_list.remove(removal) return val_list
非构建功能 如果是临时构建(仅生成 Dockerfile )、保存依赖关系、显示镜像列表、显示镜像依赖关系,则处理完后直接返回。(kolla/image/build.py): def run_build(): #... if conf.template_only: LOG.info('Dockerfiles are generated in %s', kolla.working_dir) return # We set the atime and mtime to 0 epoch to preserve allow the Docker cache # to work like we want. A different size or hash will still force a rebuild kolla.set_time() if conf.save_dependency: kolla.build_image_list() kolla.find_parents() kolla.filter_images() kolla.save_dependency(conf.save_dependency) LOG.info('Docker images dependency are saved in %s', conf.save_dependency) return if conf.list_images: kolla.build_image_list() kolla.find_parents() kolla.filter_images() kolla.list_images() return if conf.list_dependencies: kolla.build_image_list() kolla.find_parents() kolla.filter_images() kolla.list_dependencies() return 生成镜像列表: def build_image_list(self): def process_source_installation(image, section): installation = dict() # NOTE(jeffrey4l): source is not needed when the type is None if self.conf._get('type', self.conf._get_group(section)) is None: if image.parent_name is None: LOG.debug('No source location found in section %s', section) else: installation['type'] = self.conf[section]['type'] installation['source'] = self.conf[section]['location'] installation['name'] = section if installation['type'] == 'git': installation['reference'] = self.conf[section]['reference'] return installation all_sections = (set(six.iterkeys(self.conf._groups)) | set(self.conf.list_all_sections())) for path in self.docker_build_paths: # Reading parent image name with open(os.path.join(path, 'Dockerfile')) as f: content = f.read() image_name = os.path.basename(path) canonical_name = (self.namespace + '/' + self.image_prefix + image_name + ':' + self.tag) parent_search_pattern = re.compile(r'^FROM.*$', re.MULTILINE) match = re.search(parent_search_pattern, content) if match: parent_name = match.group(0).split(' ')[1] else: parent_name = '' del match image = Image(image_name, canonical_name, path, parent_name=parent_name, logger=make_a_logger(self.conf, image_name), docker_client=self.dc) if self.install_type == 'source': # NOTE(jeffrey4l): register the opts if the section didn't # register in the kolla/common/config.py file if image.name not in self.conf._groups: self.conf.register_opts(common_config.get_source_opts(), image.name) image.source = process_source_installation(image, image.name) for plugin in [match.group(0) for match in (re.search('^{}-plugin-.+'.format(image.name), section) for section in all_sections) if match]: try: self.conf.register_opts( common_config.get_source_opts(), plugin ) except cfg.DuplicateOptError: LOG.debug('Plugin %s already registered in config', plugin) image.plugins.append( process_source_installation(image, plugin)) for addition in [ match.group(0) for match in (re.search('^{}-additions-.+'.format(image.name), section) for section in all_sections) if match]: try: self.conf.register_opts( common_config.get_source_opts(), addition ) except cfg.DuplicateOptError: LOG.debug('Addition %s already registered in config', addition) image.additions.append( process_source_installation(image, addition)) self.images.append(image) 查找每个镜像的依赖关系: def find_parents(self): """Associate all images with parents and children.""" sort_images = dict() for image in self.images: sort_images[image.canonical_name] = image for parent_name, parent in sort_images.items(): for image in sort_images.values(): if image.parent_name == parent_name: parent.children.append(image) image.parent = parent 根据命令参数或者 profile 过滤掉不需要构建或处理的镜像: def filter_images(self): """Filter which images to build.""" filter_ = list() if self.regex: filter_ += self.regex elif self.conf.profile: for profile in self.conf.profile: if profile not in self.conf.profiles: self.conf.register_opt(cfg.ListOpt(profile, default=[]), 'profiles') if len(self.conf.profiles[profile]) == 0: msg = 'Profile: {} does not exist'.format(profile) raise ValueError(msg) else: filter_ += self.conf.profiles[profile] if filter_: patterns = re.compile(r"|".join(filter_).join('()')) for image in self.images: if image.status in (STATUS_MATCHED, STATUS_SKIPPED): continue if re.search(patterns, image.name): image.status = STATUS_MATCHED while (image.parent is not None and image.parent.status not in (STATUS_MATCHED, STATUS_SKIPPED)): image = image.parent if self.conf.skip_parents: image.status = STATUS_SKIPPED elif (self.conf.skip_existing and image.in_docker_cache()): image.status = STATUS_SKIPPED else: image.status = STATUS_MATCHED LOG.debug('Image %s matched regex', image.name) else: image.status = STATUS_UNMATCHED else: for image in self.images: image.status = STATUS_MATCHED 保存依赖关系: def save_dependency(self, to_file): try: import graphviz except ImportError: LOG.error('"graphviz" is required for save dependency') raise dot = graphviz.Digraph(comment='Docker Images Dependency') dot.body.extend(['rankdir=LR']) for image in self.images: if image.status not in [STATUS_MATCHED]: continue dot.node(image.name) if image.parent is not None: dot.edge(image.parent.name, image.name) with open(to_file, 'w') as f: f.write(dot.source) 显示镜像列表: def list_images(self): for count, image in enumerate([ image for image in self.images if image.status == STATUS_MATCHED ]): print(count + 1, ':', image.name) 显示镜像依赖: def list_dependencies(self): match = False for image in self.images: if image.status in [STATUS_MATCHED]: match = True if image.parent is None: base = image if not match: print('Nothing matched!') return def list_children(images, ancestry): children = six.next(iter(ancestry.values())) for image in images: if image.status not in [STATUS_MATCHED]: continue if not image.children: children.append(image.name) else: newparent = {image.name: []} children.append(newparent) list_children(image.children, newparent) ancestry = {base.name: []} list_children(base.children, ancestry) json.dump(ancestry, sys.stdout, indent=2)
创建任务队列 使用 KollaWorker 构建一个任务队列,然后根据配置的构建和 push 线程数,创建多个队列,执行其中的任务(kolla/image/build.py): def run_build(): #... push_queue = six.moves.queue.Queue() queue = kolla.build_queue(push_queue) workers = [] with join_many(workers): try: for x in six.moves.range(conf.threads): worker = WorkerThread(conf, queue) worker.setDaemon(True) worker.start() workers.append(worker) for x in six.moves.range(conf.push_threads): worker = WorkerThread(conf, push_queue) worker.setDaemon(True) worker.start() workers.append(worker) # sleep until queue is empty while queue.unfinished_tasks or push_queue.unfinished_tasks: time.sleep(3) # ensure all threads exited happily push_queue.put(WorkerThread.tombstone) queue.put(WorkerThread.tombstone) except KeyboardInterrupt: for w in workers: w.should_stop = True push_queue.put(WorkerThread.tombstone) queue.put(WorkerThread.tombstone) raise 使用 KollaWorker 对象的 build_queue 方法,为每个镜像创建一个 BuildTask 对象,并加入到任务队列中(kolla/image/build.py): class KollaWorker(object): #... def build_queue(self, push_queue): """Organizes Queue list. Return a list of Queues that have been organized into a hierarchy based on dependencies """ self.build_image_list() self.find_parents() self.filter_images() queue = six.moves.queue.Queue() for image in self.images: if image.status == STATUS_UNMATCHED: # Don't bother queuing up build tasks for things that # were not matched in the first place... (not worth the # effort to run them, if they won't be used anyway). continue if image.parent is None: queue.put(BuildTask(self.conf, image, push_queue)) LOG.info('Added image %s to queue', image.name) return queue 每个线程使用一个 WorkerThread 对象从任务队列中循环取出每个镜像关联的 BuildTask 对象,根据设置的重试次数,执行他的 run 方法进行镜像构建(kolla/image/build.py): class WorkerThread(threading.Thread): """Thread that executes tasks until the queue provides a tombstone.""" #: Object to be put on worker queues to get them to die. tombstone = object() def __init__(self, conf, queue): super(WorkerThread, self).__init__() self.queue = queue self.conf = conf self.should_stop = False def run(self): while not self.should_stop: task = self.queue.get() if task is self.tombstone: # Ensure any other threads also get the tombstone. self.queue.put(task) break try: for attempt in six.moves.range(self.conf.retries + 1): if self.should_stop: break LOG.info("Attempt number: %s to run task: %s ", attempt + 1, task.name) try: task.run() if task.success: break except Exception: LOG.exception('Unhandled error when running %s', task.name) # try again... task.reset() if task.success and not self.should_stop: for next_task in task.followups: LOG.info('Added next task %s to queue', next_task.name) self.queue.put(next_task) finally: self.queue.task_done()
执行构建任务 BuildTask 对象初始化和 run 方法,最终调用 builder 方法进行镜像构建(kolla/image/build.py): class BuildTask(DockerTask): """Task that builds out an image.""" def __init__(self, conf, image, push_queue): super(BuildTask, self).__init__() self.conf = conf self.image = image self.push_queue = push_queue self.nocache = not conf.cache self.forcerm = not conf.keep self.logger = image.logger @property def name(self): return 'BuildTask(%s)' % self.image.name def run(self): self.builder(self.image) if self.image.status in (STATUS_BUILT, STATUS_SKIPPED): self.success = True 构建时首先获取和生成源码包,最后使用 Docker 构建镜像(kolla/image/build.py): class BuildTask(DockerTask): #... def builder(self, image): def make_an_archive(items, arcname, item_child_path=None): if not item_child_path: item_child_path = arcname archives = list() items_path = os.path.join(image.path, item_child_path) for item in items: archive_path = self.process_source(image, item) if image.status in STATUS_ERRORS: raise ArchivingError archives.append(archive_path) if archives: for archive in archives: with tarfile.open(archive, 'r') as archive_tar: archive_tar.extractall(path=items_path) else: try: os.mkdir(items_path) except OSError as e: if e.errno == errno.EEXIST: self.logger.info( 'Directory %s already exist. Skipping.', items_path) else: self.logger.error('Failed to create directory %s: %s', items_path, e) image.status = STATUS_CONNECTION_ERROR raise ArchivingError arc_path = os.path.join(image.path, '%s-archive' % arcname) with tarfile.open(arc_path, 'w') as tar: tar.add(items_path, arcname=arcname) return len(os.listdir(items_path)) self.logger.debug('Processing') if image.status == STATUS_SKIPPED: self.logger.info('Skipping %s' % image.name) return if image.status == STATUS_UNMATCHED: return if (image.parent is not None and image.parent.status in STATUS_ERRORS): self.logger.error('Parent image error\'d with message "%s"', image.parent.status) image.status = STATUS_PARENT_ERROR return image.status = STATUS_BUILDING self.logger.info('Building') if image.source and 'source' in image.source: self.process_source(image, image.source) if image.status in STATUS_ERRORS: return if self.conf.install_type == 'source': try: plugins_am = make_an_archive(image.plugins, 'plugins') except ArchivingError: self.logger.error( "Failed turning any plugins into a plugins archive") return else: self.logger.debug( "Turned %s plugins into plugins archive", plugins_am) try: additions_am = make_an_archive(image.additions, 'additions') except ArchivingError: self.logger.error( "Failed turning any additions into a additions archive") return else: self.logger.debug( "Turned %s additions into additions archive", additions_am) # Pull the latest image for the base distro only pull = self.conf.pull if image.parent is None else False buildargs = self.update_buildargs() try: for response in self.dc.build(path=image.path, tag=image.canonical_name, nocache=not self.conf.cache, rm=True, pull=pull, forcerm=self.forcerm, buildargs=buildargs): stream = json.loads(response.decode('utf-8')) if 'stream' in stream: for line in stream['stream'].split('\n'): if line: self.logger.info('%s', line) if 'errorDetail' in stream: image.status = STATUS_ERROR self.logger.error('Error\'d with the following message') for line in stream['errorDetail']['message'].split('\n'): if line: self.logger.error('%s', line) return except docker.errors.DockerException: image.status = STATUS_ERROR self.logger.exception('Unknown docker error when building') except Exception: image.status = STATUS_ERROR self.logger.exception('Unknown error when building') else: image.status = STATUS_BUILT self.logger.info('Built') 获取和生成源码包时,根据源码来源的不同使用不同方法,最终的源码包名字都是 镜像名 加上 -archive (如 openstack-base-archive ),在生成镜像时复制解压进镜像里(kolla/image/build.py): class BuildTask(DockerTask): #... def process_source(self, image, source): dest_archive = os.path.join(image.path, source['name'] + '-archive') if source.get('type') == 'url': self.logger.debug("Getting archive from %s", source['source']) try: r = requests.get(source['source'], timeout=self.conf.timeout) except requests_exc.Timeout: self.logger.exception( 'Request timed out while getting archive from %s', source['source']) image.status = STATUS_ERROR return if r.status_code == 200: with open(dest_archive, 'wb') as f: f.write(r.content) else: self.logger.error( 'Failed to download archive: status_code %s', r.status_code) image.status = STATUS_ERROR return elif source.get('type') == 'git': clone_dir = '{}-{}'.format(dest_archive, source['reference'].replace('/', '-')) if os.path.exists(clone_dir): self.logger.info("Clone dir %s exists. Removing it.", clone_dir) shutil.rmtree(clone_dir) try: self.logger.debug("Cloning from %s", source['source']) git.Git().clone(source['source'], clone_dir) git.Git(clone_dir).checkout(source['reference']) reference_sha = git.Git(clone_dir).rev_parse('HEAD') self.logger.debug("Git checkout by reference %s (%s)", source['reference'], reference_sha) except Exception as e: self.logger.error("Failed to get source from git", image.name) self.logger.error("Error: %s", e) # clean-up clone folder to retry shutil.rmtree(clone_dir) image.status = STATUS_ERROR return with tarfile.open(dest_archive, 'w') as tar: tar.add(clone_dir, arcname=os.path.basename(clone_dir)) elif source.get('type') == 'local': self.logger.debug("Getting local archive from %s", source['source']) if os.path.isdir(source['source']): with tarfile.open(dest_archive, 'w') as tar: tar.add(source['source'], arcname=os.path.basename(source['source'])) else: shutil.copyfile(source['source'], dest_archive) else: self.logger.error("Wrong source type '%s'", source.get('type')) image.status = STATUS_ERROR return # Set time on destination archive to epoch 0 os.utime(dest_archive, (0, 0)) return dest_archive
云计算
2018-04-01 20:41:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
流量镜像
流量镜像,也称为影子流量,流量镜像提供一种尽可能低的风险为生产带来变化的强大功能。镜像会将实时流量的副本发送到镜像服务。镜像流量发生在主服务的关键请求路径之外。
在非生产或者测试环境中,尝试访问一个服务所有可能的测试用例组合是个非常不现实的任务。 在某些情况下,编写这些用例的所有工作也可能与实际生产所需的用例不匹配。在理想情况下,可以使用实时的生产用例和流量来帮助完善在测试环境中错过的功能区域。
一旦我们能够可靠地镜像流量,就可以开始做一些有价值的事情,例如通过请求流量对比工具Diffy,可以将引入测试集群的流量与生产集群中的预期行为进行比较。例如,我们可能想比较请求结果与预期结果间的偏差,或是API协议中的数据损坏情况,以便更好地兼容。
除此之外,需要注意: 当流量镜像到不同的服务时,会发生在请求的关键路径之外; 忽略对任何镜像流量的响应; 流量被视为“即发即忘”;
流量对比
此处,插入一个代理就可以负责此类流量的协调,并对其进行有趣的比较。 Diffy 就是一款这样的代理工具。Diffy启动一个代理服务(例如监听端口8880),再根据用户设置的primary、secondary两个旧服务地址(primary和secondary代码完全相同,目的是为了减少噪音干扰)、candidate新服务地址。
它还能够检测结果中的噪音,并通过先调用两个实时服务的实例来忽略它们(例如时间戳,单调递增计数器等提示),总结来说就是检测,然后在测试服务中忽略掉这部分。
Diffy还提供了一个不错的页面可以用来查看调用结果、对比情况、和基于某些特征的过滤。它还有一个很好的管理控制台,可以查看有关调用比较结果的功能指标(metrics)和统计数据(statistics)。
创建用于Istio流量镜像的服务
在此任务中,将首先强制所有流量到 v1 版本的服务。然后,将使用规则将一部分流量镜像到 v2版本。
首先部署两个版本的示例服务。
版本1的部署使用了Docker镜像httpbin,提供常见的http请求访问: apiVersion: extensions/v1beta1 kind: Deployment metadata: name: mirrorservice-sample-v1 spec: replicas: 1 template: metadata: labels: app: mirrorservice-sample version: v1 spec: containers: - image: docker.io/kennethreitz/httpbin imagePullPolicy: IfNotPresent name: mirrorservice-sample command: ["gunicorn", "--access-logfile", "-", "-b", "0.0.0.0:44134", "httpbin:app"] ports: - containerPort: 44134
版本2的部署使用了自定义的Docker镜像,对应的Dockerfile如下: FROM nginx:latest COPY default.conf /etc/nginx/conf.d/ EXPOSE 80
所需的nginx 配置文件: server { listen 44134; server_name localhost; location / { proxy_pass http://httpbin-diffy.diffy:8880/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } }
版本2的部署作为Istio的流量镜像目标,在接收到流量之后会转发到Diffy的代理中。当前没有直接将Diffy代理作为Isito流量镜像目标,原因是Diffy代理与Envoy代理目前本身有冲突,无法正常流量转发,因此需要此部署中转一下。 apiVersion: extensions/v1beta1 kind: Deployment metadata: name: mirrorservice-sample-v2 spec: replicas: 1 template: metadata: labels: app: mirrorservice-sample version: v2 spec: containers: - name: mirrorservice-sample image: registry.cn-beijing.aliyuncs.com/wangxining/mirrorservice:0.1 imagePullPolicy: Always ports: - containerPort: 44134
对应的Kubernetes service: apiVersion: v1 kind: Service metadata: name: mirrorservice-sample spec: type: ClusterIP ports: - name: http port: 44134 selector: app: mirrorservice-sample
创建流量镜像的Istio策略
默认情况下,Kubernetes 在服务的两个版本之间进行负载均衡。创建如下流量镜像规则将 100% 的流量发送到 v1, 同时指定流量镜像到v2。当流量被镜像时,请求将通过其主机/授权报头发送到镜像服务附上 -shadow 。 apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: mirrorservice-sample spec: host: mirrorservice-sample subsets: - name: v1 labels: version: v1 - name: v2 labels: version: v2 --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: mirrorservice-sample spec: hosts: - mirrorservice-sample http: - route: - destination: host: mirrorservice-sample subset: v1 weight: 100 #- destination: # host: mirrorservice-sample # subset: v2 # weight: 0 mirror: host: mirrorservice-sample subset: v2
搭建Diffy用于请求流量对比
Diffy可以作为代理,截取请求并发送至所有运行的服务实例,通过对比响应结果来发现每次迭代代码中可能存在的问题。其中,Diffy上运行了三类代码实例: 线上稳定版本:一个运行线上稳定版本代码的节点 线上稳定版本备份:同样运行了线上的稳定版本,用于消除噪音 测试版本:待上线的测试版本,用于和线上环境代码进行对比
在实际Diffy测试中,会发现大部分的接口都会有一定差异,原因是这些响应中存在了噪音,噪音可能包括: server响应中生成的时间戳 随机生成的数字 系统服务间的有条件竞争
Diffy能够通过一定的方式,清除这类噪音,保证分析结果不被影响。
创建Diffy及示例服务
通过以下YAML创建Diffy服务: apiVersion: v1 kind: Service metadata: name: httpbin-diffy labels: app: httpbin-diffy spec: ports: - name: http-proxy port: 8880 - name: http-admin port: 8881 - name: http-console port: 8888 selector: app: httpbin-diffy --- apiVersion: extensions/v1beta1 kind: Deployment metadata: labels: app: httpbin-diffy version: v2 name: httpbin-diffy-v2 spec: replicas: 1 selector: matchLabels: app: httpbin-diffy version: v2 template: metadata: labels: app: httpbin-diffy version: v2 spec: containers: - image: lordofthejars/diffy:1.0 imagePullPolicy: IfNotPresent livenessProbe: exec: command: - curl - localhost:8888 initialDelaySeconds: 10 periodSeconds: 60 timeoutSeconds: 1 name: httpbin-diffy args: ["-candidate=httpbin-candidate:8080", "-master.primary=httpbin-master:8080", "-master.secondary=httpbin-master:8080", "-service.protocol=http", "-serviceName=httpbin", "-proxy.port=:8880", "-admin.port=:8881", "-http.port=:8888", "-rootUrl='localhost:8888'"] ports: - containerPort: 8888 name: http-console protocol: TCP - containerPort: 8880 name: http-proxy protocol: TCP - containerPort: 8881 name: http-admin protocol: TCP readinessProbe: exec: command: - curl - localhost:8888 initialDelaySeconds: 10 periodSeconds: 60 timeoutSeconds: 1 securityContext: privileged: false
通过以下YAML创建示例所用的primary、secondary(当前示例中与primary相同)与candidate服务: apiVersion: v1 kind: Service metadata: name: httpbin-master labels: app: httpbin-master spec: ports: - name: http port: 8080 selector: app: httpbin version: v1 --- apiVersion: v1 kind: Service metadata: name: httpbin-candidate labels: app: httpbin-candidate spec: ports: - name: http port: 8080 selector: app: httpbin version: v2 --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: httpbin-v1 spec: replicas: 1 template: metadata: labels: app: httpbin version: v1 spec: containers: - image: docker.io/kennethreitz/httpbin imagePullPolicy: IfNotPresent name: httpbin command: ["gunicorn", "--access-logfile", "-", "-b", "0.0.0.0:8080", "httpbin:app"] ports: - containerPort: 8080 --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: httpbin-v2 spec: replicas: 1 template: metadata: labels: app: httpbin version: v2 spec: containers: - image: docker.io/kennethreitz/httpbin imagePullPolicy: IfNotPresent name: httpbin command: ["gunicorn", "--access-logfile", "-", "-b", "0.0.0.0:8080", "httpbin:app"] ports: - containerPort: 8080
发送流量进行镜像验证
启动 sleep 服务,这样就可以使用 curl 来提供负载: cat <进入到SLEEP_POD, 具体POD名称根据实际赋值。 kubectl exec -it $SLEEP_POD -c sleep sh
发送流量: curl -v http://mirrorservice-sample:44134/headers
可以查看 v1的访问日志记录,如下所示创建的请求100%指向了v1。
与此同时,查看Diffy的Web界面,可以看到创建的请求也被镜像到Diffy Proxy:

Diffy能够通过一定的方式,清除这类噪音,保证分析结果不被影响。
结论
流量镜像提供一种尽可能低的风险为生产带来变化的强大功能。镜像会将实时流量的副本发送到镜像服务,镜像流量发生在主服务的关键请求路径之外。一旦我们能够可靠地镜像流量,就可以开始做一些有价值的事情,例如通过请求流量对比工具Diffy,可以将引入测试集群的流量与生产集群中的预期行为进行比较。
支持流量镜像只是 Istio 的众多功能之一,它将使基于大型微服务的应用程序的生产部署与管理变得更加简单。欢迎大家使用阿里云上的容器服务,快速搭建微服务的开放治理平台Istio,比较简单地集成到自己项目的微服务开发中。
作者: osswangxining
原文链接
本文为云栖社区原创内容,未经允许不得转载。
云计算
2019-01-28 16:31:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
11月13~15日,KubeCon 上海大会召开,云原生是这个秋天最火热的技术。很多同学来问如何上手 Kubernetes和Istio 服务网格开发。本文将帮助你利用Docker CE桌面版,15分钟在笔记本上从零搭建 Kubernetes + Istio开发环境,开启云原生之旅。
说明:本文测试通过环境 Docker CE 18.09 (Kubernetes 1.10.3) 以及 Istio 1.0.4
先决条件 ,你需要一个 Docker for Mac或者Docker for Windows的安装包,如果没有请下载 下载 Docker CE最新版本 。由于Kubernetes大量的容器镜像在 gcr.io, 无法在国内保证稳定的访问。我们提供了一些 工具脚本 ,帮助从阿里云镜像服务下载所需镜像
首先, git clone https://github.com/AliyunContainerService/k8s-for-docker-desktop cd k8s-for-docker-desktop
Docker for Mac 开启 Kubernetes
为 Docker daemon 配置 Docker Hub 的中国官方镜像加速 https://registry.docker-cn.com
可选操作: 为 Kubernetes 配置 CPU 和 内存资源,建议分配 4GB 或更多内存。
预先从阿里云Docker镜像服务下载 Kubernetes 所需要的镜像, 可以通过修改 images.properties 文件加载你自己需要的镜像 ./load_images.sh
开启 Kubernetes,并等待 Kubernetes 开始运行
Docker for Windows 开启 Kubernetes
为 Docker daemon 配置 Docker Hub 的中国官方镜像加速 https://registry.docker-cn.com
可选操作: 为 Kubernetes 配置 CPU 和 内存资源,建议分配 4GB 或更多内存。
预先从阿里云Docker镜像服务下载 Kubernetes 所需要的镜像, 可以通过修改 images.properties 文件加载你自己需要的镜像
使用 Bash shell ./load_images.sh
使用 PowerShell .\load_images.ps1
说明: 如果因为安全策略无法执行 PowerShell 脚本,请在 “以管理员身份运行” 的 PowerShell 中执行 Set-ExecutionPolicy RemoteSigned 命令。
开启 Kubernetes,并等待 Kubernetes 开始运行
配置 Kubernetes
可选操作: 切换Kubernetes运行上下文至 docker-for-desktop kubectl config use-context docker-for-desktop
验证 Kubernetes 集群状态 kubectl cluster-info kubectl get nodes
部署 Kubernetes dashboard kubectl create -f https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml
或 kubectl create -f kubernetes-dashboard.yaml
开启 API Server 访问代理 kubectl proxy
通过如下 URL 访问 Kubernetes dashboard
http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/#!/overview?namespace=default
说明:如果测试 Istio,不需要安装 Ingress,如果需要 Ingress 可以参考 https://github.com/AliyunContainerService/k8s-for-docker-desktop 中 Ingress相关章节
安装 Helm
可以根据文档安装 helm https://github.com/helm/helm/blob/master/docs/install.md
在 Mac OS 上安装 # Use homebrew on Mac brew install kubernetes-helm # Install Tiller into your Kubernetes cluster helm init --upgrade -i registry.cn-hangzhou.aliyuncs.com/google_containers/tiller:v2.11.0 --skip-refresh # update charts repo (Optional) helm repo update
在Windows上安装 # Use Chocolatey on Windows choco install kubernetes-helm # Install Tiller into your Kubernetes cluster helm init --upgrade -i registry.cn-hangzhou.aliyuncs.com/google_containers/tiller:v2.11.0 --skip-refresh # update charts repo (Optional) helm repo update
配置 Istio
可以根据文档安装 Istio https://istio.io/docs/setup/kubernetes/
下载 Istio 1.0.4 并安装 CLI curl -L https://git.io/getLatestIstio | sh - cd istio-1.0.4/ export PATH=$PWD/bin:$PATH
在Windows上,您可以手工下载Istio安装包,或者把 getLatestIstio.ps1 拷贝到你希望下载 Istio 的目录,并执行 - 说明:根据社区提供的 安装脚本 修改而来 .\getLatestIstio.ps1
通过 Helm chart 安装 Istio helm install install/kubernetes/helm/istio --name istio --namespace istio-system
查看 istio 发布状态 helm status istio
为 default 名空间开启自动 sidecar 注入 kubectl label namespace default istio-injection=enabled kubectl get namespace -L istio-injection
安装 Book Info 示例 kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yaml
确认示例应用在运行中 export GATEWAY_URL=localhost:80 curl -o /dev/null -s -w "%{http_code}\n" http://${GATEWAY_URL}/productpage
可以通过浏览器访问
http://localhost/productpage
说明:如果当前80端口已经被占用或保留,我们可以编辑 install/kubernetes/helm/istio/values.yaml 文件中
Gateway 端口进行调整,比如将 80 端口替换为 8888 端口 ## You can add custom gateway ports - port: 8888 # Changed from 80 targetPort: 80 name: http2 nodePort: 31380
然后执行如下命令并生效 kubectl delete service istio-ingressgateway -n istio-system helm upgrade istio install/kubernetes/helm/istio
学习 Istio
大家参照 Istio 开始学习吧, https://istio.io/zh/docs/examples/bookinfo/
删除实例应用 samples/bookinfo/platform/kube/cleanup.sh
卸载 Istio helm del --purge istio kubectl delete -f install/kubernetes/helm/istio/templates/crds.yaml -n istio-system
总结
阿里云Kubernetes服务 (ACK) 已经对 Istio提供了支持,并将Istio和阿里云进行了深度整合,比如可以通过阿里云日志服务支持基于Jaeger的全链路监控,基于云监控提升Istio可观测性和报警能力等。后续也将进一步简化Kubernetes和Istio的开发者体验,让大家方便地把 Kubernetes + Istio 应用无缝迁移上云。
拓展阅读 Kubernetes 中文文档 Istio 中文文档 Istio官方文档 - 阿里云Kubernetes服务 (ACK)支持 阿里云 Kubernetes服务上的Istio实践 阿里云容器服务团队博客 Istio 实践系列文章 - 持续更新中 ServiceMesher社区
作者: 易立
原文链接
本文为云栖社区原创内容,未经允许不得转载。
云计算
2019-01-28 16:03:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
基本介绍
经常会碰到客户询问,为什么我设置了autoplay为true,但是没有自动播放,每次都要向客户解释这个是浏览器从用户体验角度考虑做的限制,客户会继续询问那我要怎么做? 针对这个问题Aliplayer也专们做过优化,提供一些方式让客户能更好的处理这种情况。
现象描述
有声音视频无法自动播放这个在移动端上一直都是这个限制,桌面版的Safari在2017年的11版本也宣布禁掉带有声音的多媒体自动播放功能,Chrome也在2018年4月份发布的66版本也正式关掉了声音自动播放,也就是说通过H5方式播放音视频在桌面版浏览器自动播放会失效。

浏览器厂家为什么要禁止自动播放的呢? 移动端主要考虑的是手机的带宽以及对电池的消耗,Chrome主要基于下面的考虑: improve the user experience minimize incentives to install ad blockers reduce data consumption on expensive and/or constrained networks 总之一句话,从用户角度考虑。
破解之法
只要视频没有声音或者有用户交互了就可以播放, 现在来看一下Safari和Chrome的具体政策:
Safari允许自动播放政策,具体请查看 Safari Video Policy : 视频没有音轨 Video设置为muted,