Kubernetes的容器存储接口(CSI)GA了

作者:Saad Ali,Google高级软件工程师Kubernetes实施的容器存储接口(CSI)已在Kubernetes v1.13版本中升级为GA。CSI的支持在Kubernetes v1.9版本中作为alpha引入,并在Kubernetes v1.10版本中升级为beta。GA里程碑表明Kubernetes用户可能依赖于该功能及其API,而不必担心将来回归(regression)导致的向后不兼容的更改。GA功能受Kubernetes弃用(deprecation)政策保护。为何选择CSI?虽然在CSI之前,Kubernetes提供了一个功能强大的卷插件系统,但是在Kubernetes添加对新卷插件的支持是一项挑战:卷插件是“树内”(“in-tree”),这意味着他们的代码是核心Kubernetes代码的一部分,并随核心Kubernetes一起提供二进制文件。希望向Kubernetes添加对其存储系统的支持(或修复现有卷插件中的错误)的供应商被迫与Kubernetes发布流程保持一致。此外,第三方存储代码导致核心Kubernetes二进制文件中的可靠性和安全性问题,代码通常很难(在某些情况下不可能)让Kubernetes维护者进行测试和维护。CSI是作为将任意块和文件存储存储系统暴露于容器编排系统(CO)上,如Kubernetes,的容器化工作负载的标准而开发的。随着容器存储接口的采用,Kubernetes卷层变得真正可扩展。使用CSI,第三方存储供应商可以编写和部署插件,在Kubernetes中暴露新的存储系统,而无需触及核心Kubernetes代码。这为Kubernetes用户提供了更多存储选项,使系统更加安全可靠。新的改变?随着升级到GA,Kubernetes对CSI的实施引入了以下变化:Kubernetes现在与CSI spec v1.0和v0.3兼容(而不是CSI spec v0.2)。CSI spec v0.3和v1.0之间存在重大变化,但Kubernetes v1.13支持这两个版本,因此任何一个版本都适用于Kubernetes v1.13。请注意,随着CSI 1.0 API的发布,使用0.3或更老版本CSI API的CSI驱动程序被弃用(deprecated),并计划在Kubernetes v1.15中删除。CSI spec v0.2和v0.3之间没有重大变化,因此v0.2驱动程序也应该与Kubernetes v1.10.0+一起使用。CSI规范v0.1和v0.2之间存在重大变化,因此在使用Kubernetes v1.10.0+之前,必须将实现非常旧的CSI 0.1驱动程序更新为至少0.2兼容。Kubernetes VolumeAttachment对象(在v1.9 storage v1alpha1 group引入,并在v1.10中添加到v1beta1 group)在v1.13已添加到的storage v1 group。Kubernetes CSIPersistentVolumeSource卷类型已升级为GA。Kubelet设备插件注册机制,即kubelet发现新CSI驱动程序的方式,已在Kubernetes v1.13中提升为GA。如何部署CSI驱动程序?对如何在Kubernetes上部署,或管理现有CSI驱动程序感兴趣的Kubernetes用户,应该查看CSI驱动程序作者提供的文档。如何使用CSI卷?假设CSI存储插件已部署在Kubernetes集群上,用户可以通过熟悉的Kubernetes存储API对象使用CSI卷:PersistentVolumeClaims,PersistentVolumes和StorageClasses。文档在这里。虽然Kubernetes实施CSI是Kubernetes v1.13中的GA功能,但它可能需要以下标志:API服务器二进制文件和kubelet二进制文件:–allow-privileged=true大多数CSI插件都需要双向安装传播(bidirectional mount propagation),只能在特权(privileged)pod启用。只有在此标志设置为true的群集上才允许使用特权pod,这是某些环境(如GCE,GKE和kubeadm)的默认设置。动态配置你可以通过创建指向CSI插件的StorageClass,为支持动态配置(dynamic provisioning)的CSI Storage插件启用卷的自动创建/删除(creation/deletion)。例如,以下StorageClass通过名为“csi-driver.example.com”的CSI卷插件,动态创建“fast-storage”卷。kind: StorageClassapiVersion: storage.k8s.io/v1metadata: name: fast-storageprovisioner: csi-driver.example.comparameters: type: pd-ssd csi.storage.k8s.io/provisioner-secret-name: mysecret csi.storage.k8s.io/provisioner-secret-namespace: mynamespaceGA的新功能,CSI的external-provisioner外部配置商(v1.0.1+)保留以csi.storage.k8s.io/为前缀的参数键。如果密钥(key)不对应于一组已知密钥,则简单地忽略这些值(并且不将其传递给CSI驱动程序)。CSI外部配置商v1.0.1也支持旧的秘密参数密钥(csiProvisionerSecretName,csiProvisionerSecretNamespace等),但被弃用(deprecated),可能会在CSI外部配置商的未来版本中删除。动态配置由PersistentVolumeClaim对象的创建触发。例如,以下PersistentVolumeClaim使用上面的StorageClass触发动态配置。apiVersion: v1kind: PersistentVolumeClaimmetadata: name: my-request-for-storagespec: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi storageClassName: fast-storage调用卷配置时,参数类型:pd-ssd和秘密通过CreateVolume调用,递给CSI插件csi-driver.example.com。作为响应,外部卷插件提供新卷,然后自动创建PersistentVolume对象以表示新卷。然后,Kubernetes将新的PersistentVolume对象绑定到PersistentVolumeClaim,使其可以使用。如果快速存储(fast-storage)StorageClass标记为“default”,则不需要在PersistentVolumeClaim中包含storageClassName,默认情况下将使用它。预先配置的卷你可以通过手动创建PersistentVolume对象来表示现有卷,从而在Kubernetes中暴露预先存在的卷。例如,以下PersistentVolume暴露名为“existingVolumeName”的卷,该卷属于名为“csi-driver.example.com”的CSI存储插件。apiVersion: v1kind: PersistentVolumemetadata: name: my-manually-created-pvspec: capacity: storage: 5Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain csi: driver: csi-driver.example.com volumeHandle: existingVolumeName readOnly: false fsType: ext4 volumeAttributes: foo: bar controllerPublishSecretRef: name: mysecret1 namespace: mynamespace nodeStageSecretRef: name: mysecret2 namespace: mynamespace nodePublishSecretRef name: mysecret3 namespace: mynamespace连接(Attaching)和安装(Mounting)你可以在任何pod或pod模板中引用绑定到CSI卷的PersistentVolumeClaim。kind: PodapiVersion: v1metadata: name: my-podspec: containers: - name: my-frontend image: nginx volumeMounts: - mountPath: “/var/www/html” name: my-csi-volume volumes: - name: my-csi-volume persistentVolumeClaim: claimName: my-request-for-storage当引用CSI卷的pod被调度时,Kubernetes将针对外部CSI插件(ControllerPublishVolume、NodeStageVolume、NodePublishVolume等)触发相应的操作,以确保指定的卷被连接(attached)和安装(mounted),并准备好给pod里的容器使用。有关详细信息,请参阅CSI实施设计文档和文档。如何编写CSI驱动程序?kubernetes-csi网站详细介绍了如何在Kubernetes上开发、部署和测试CSI驱动程序。一般而言,CSI驱动程序应与Kubernetes一起部署以下侧车/辅助(sidercar/helper)容器:external-attacher观察Kubernetes VolumeAttachment对象,并触发针对CSI端点的ControllerPublish和ControllerUnpublish操作。external-provisioner观察Kubernetes PersistentVolumeClaim对象,并触发针对CSI端点的CreateVolume和DeleteVolume操作。node-driver-registrar通过Kubelet设备插件机制,使用kubelet注册CSI驱动程序。cluster-driver-registrar (Alpha)通过创建CSIDriver对象,向Kubernetes集群注册CSI驱动程序,该对象使驱动程序能够自定义Kubernetes与其交互的方式。external-snapshotter (Alpha)观察Kubernetes VolumeSnapshot CRD对象,并触发针对CSI端点的CreateSnapshot和DeleteSnapshot操作。livenessprobe可以包含在CSI插件pod中,以启用Kubernetes Liveness Probe机制。存储供应商可以使用这些组件为其插件构建Kubernetes部署,而他们的CSI驱动程序完全不需知道Kubernetes。CSI驱动程序列表CSI驱动程序由第三方开发和维护。你可以在此处找到CSI驱动程序的列表。树内(in-tree)卷插件怎么样?有计划将大多数持久的远程树内卷插件迁移到CSI。有关详细信息,请参阅设计文档。GA的限制CSI的GA实施具有以下限制:短暂(Ephemeral)的本地卷必须创建PVC(不支持pod内联引用CSI卷)。下一步?致力于移动Kubernetes CSI的alpha功能到beta:Raw block volumes拓扑感知。Kubernetes理解和影响CSI卷的配置位置(zone可用区,region地域等)的能力。取决于CSI CRD的功能(例如“跳过附加”和“挂载时的Pod信息”)。卷快照努力完成对本地短暂卷的支持。将远程持久性树内卷插件迁移到CSI。怎样参与?Slack频道wg-csi和谷歌讨论区kubernetes-sig-storage-wg-csi,以及任何标准的SIG存储通信渠道都是接触SIG存储团队的绝佳媒介。像Kubernetes一样,这个项目是许多来自不同背景的贡献者共同努力的结果。我们非常感谢本季度主动帮助项目达成GA的新贡献者:Saad Ali (saad-ali)Michelle Au (msau42)Serguei Bezverkhi (sbezverk)Masaki Kimura (mkimuram)Patrick Ohly (pohly)Luis Pabón (lpabon)Jan Šafránek (jsafrane)Vladimir Vivien (vladimirvivien)Cheng Xing (verult)Xing Yang (xing-yang)David Zhu (davidz627)如果你有兴趣参与CSI或Kubernetes存储系统的任何部分的设计和开发,请加入Kubernetes存储特别兴趣小组(SIG)。我们正在快速成长,一直欢迎新的贡献者。2019年KubeCon + CloudNativeCon中国论坛提案征集(CFP)现已开放KubeCon + CloudNativeCon 论坛让用户、开发人员、从业人员汇聚一堂,面对面进行交流合作。与会人员有 Kubernetes、Prometheus 及其他云原生计算基金会 (CNCF) 主办项目的领导,和我们一同探讨云原生生态系统发展方向。2019年中国开源峰会提案征集(CFP)现已开放在中国开源峰会上,与会者将共同合作及共享信息,了解最新和最有趣的开源技术,包括 Linux、容器、云技术、网络、微服务等;并获得如何在开源社区中导向和引领的信息。大会日期:提案征集截止日期:太平洋标准时间 2 月 15 日,星期五,晚上 11:59提案征集通知日期:2019 年 4 月 1 日会议日程通告日期:2019 年 4 月 3 日幻灯片提交截止日期:6 月 17 日,星期一会议活动举办日期:2019 年 6 月 24 至 26 日2019年KubeCon + CloudNativeCon + Open Source Summit China赞助方案出炉啦 ...

January 22, 2019 · 1 min · jiezi

容器监控实践—node-exporter

概述Prometheus从2016年加入CNCF,到2018年8月毕业,现在已经成为Kubernetes的官方监控方案,接下来的几篇文章将详细解读Promethues(2.x)Prometheus可以从Kubernetes集群的各个组件中采集数据,比如kubelet中自带的cadvisor,api-server等,而node-export就是其中一种来源Exporter是Prometheus的一类数据采集组件的总称。它负责从目标处搜集数据,并将其转化为Prometheus支持的格式。与传统的数据采集组件不同的是,它并不向中央服务器发送数据,而是等待中央服务器主动前来抓取,默认的抓取地址为http://CURRENT_IP:9100/metricsnode-exporter用于采集服务器层面的运行指标,包括机器的loadavg、filesystem、meminfo等基础监控,类似于传统主机监控维度的zabbix-agentnode-export由prometheus官方提供、维护,不会捆绑安装,但基本上是必备的exporter功能node-exporter用于提供NIX内核的硬件以及系统指标。如果是windows系统,可以使用WMI exporter如果是采集NVIDIA的GPU指标,可以使用prometheus-dcgm 根据不同的NIX操作系统,node-exporter采集指标的支持也是不一样的,如:diskstats 支持 Darwin, Linuxcpu 支持Darwin, Dragonfly, FreeBSD, Linux, Solaris等,详细信息参考:node_exporter我们可以使用 –collectors.enabled参数指定node_exporter收集的功能模块,或者用–no-collector指定不需要的模块,如果不指定,将使用默认配置。部署二进制部署:下载地址:从https://github.com/prometheus…解压文件:tar -xvzf .tar.gz开始运行:./node_exporter./node_exporter -h 查看帮助usage: node_exporter [<flags>]Flags: -h, –help –collector.diskstats.ignored-devices –collector.filesystem.ignored-mount-points –collector.filesystem.ignored-fs-types –collector.netdev.ignored-devices –collector.netstat.fields –collector.ntp.server=“127.0.0.1” ……/node_exporter运行后,可以访问http://${IP}:9100/metrics,就会展示对应的指标列表Docker安装:docker run -d \ –net=“host” \ –pid=“host” \ -v “/:/host:ro,rslave” \ quay.io/prometheus/node-exporter \ –path.rootfs /hostk8s中安装:node-exporter.yaml文件:apiVersion: v1kind: Servicemetadata: annotations: prometheus.io/scrape: ’true’ labels: app: node-exporter name: node-exporter name: node-exporterspec: clusterIP: None ports: - name: scrape port: 9100 protocol: TCP selector: app: node-exporter type: ClusterIP—-apiVersion: extensions/v1beta1kind: DaemonSetmetadata: name: node-exporterspec: template: metadata: labels: app: node-exporter name: node-exporter spec: containers: - image: registry.cn-hangzhou.aliyuncs.com/tryk8s/node-exporter:latest name: node-exporter ports: - containerPort: 9100 hostPort: 9100 name: scrape hostNetwork: true hostPID: truekubectl create -f node-exporter.yaml得到一个daemonset和一个service对象,部署后,为了能够让Prometheus能够从当前node exporter获取到监控数据,这里需要修改Prometheus配置文件。编辑prometheus.yml并在scrape_configs节点下添加以下内容:scrape_configs: # 采集node exporter监控数据 - job_name: ’node’ static_configs: - targets: [’localhost:9100’]也可以使用prometheus.io/scrape: ’true’标识来自动获取service的metric接口- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]配置完成后,重启prometheus就能看到对应的指标查看指标:直接查看:如果是二进制或者docker部署,部署成功后可以访问:http://${IP}:9100/metrics会输出下面格式的内容,包含了node-exporter暴露的所有指标:# HELP go_gc_duration_seconds A summary of the GC invocation durations.# TYPE go_gc_duration_seconds summarygo_gc_duration_seconds{quantile=“0”} 6.1872e-05go_gc_duration_seconds{quantile=“0.25”} 0.000119463go_gc_duration_seconds{quantile=“0.5”} 0.000151156go_gc_duration_seconds{quantile=“0.75”} 0.000198764go_gc_duration_seconds{quantile=“1”} 0.009889647go_gc_duration_seconds_sum 0.257232201go_gc_duration_seconds_count 1187# HELP node_cpu Seconds the cpus spent in each mode.# TYPE node_cpu counternode_cpu{cpu=“cpu0”,mode=“guest”} 0node_cpu{cpu=“cpu0”,mode=“guest_nice”} 0node_cpu{cpu=“cpu0”,mode=“idle”} 68859.19node_cpu{cpu=“cpu0”,mode=“iowait”} 167.22node_cpu{cpu=“cpu0”,mode=“irq”} 0node_cpu{cpu=“cpu0”,mode=“nice”} 19.92node_cpu{cpu=“cpu0”,mode=“softirq”} 17.05node_cpu{cpu=“cpu0”,mode=“steal”} 28.1Prometheus查看:类似go_gc_duration_seconds和node_cpu就是metric的名称,如果使用了Prometheus,则可以在http://${IP}:9090/页面的指标中搜索到以上的指标:常用指标类型有:node_cpu:系统CPU使用量node_disk:磁盘IOnode_filesystem:文件系统用量node_load1:系统负载node_memeory*:内存使用量node_network*:网络带宽node_time:当前系统时间go_:node exporter中go相关指标process_:node exporter自身进程相关运行指标Grafana查看:Prometheus虽然自带了web页面,但一般会和更专业的Grafana配套做指标的可视化,Grafana有很多模板,用于更友好地展示出指标的情况,如Node Exporter for Prometheus在grafana中配置好变量、导入模板就会有上图的效果。深入解读node-exporter是Prometheus官方推荐的exporter,类似的还有HAProxy exporterCollectd exporterSNMP exporterMySQL server exporter….官方推荐的都会在https://github.com/prometheus下,在exporter推荐页,也会有很多第三方的exporter,由个人或者组织开发上传,如果有自定义的采集需求,可以自己编写exporter,具体的案例可以参考后续的[自定义Exporter]文章版本问题因为node_exporter是比较老的组件,有一些最佳实践并没有merge进去,比如符合Prometheus命名规范(https://prometheus.io/docs/pr…,目前(2019.1)最新版本为0.17一些指标名字的变化(详细比对)* node_cpu -> node_cpu_seconds_total* node_memory_MemTotal -> node_memory_MemTotal_bytes* node_memory_MemFree -> node_memory_MemFree_bytes* node_filesystem_avail -> node_filesystem_avail_bytes* node_filesystem_size -> node_filesystem_size_bytes* node_disk_io_time_ms -> node_disk_io_time_seconds_total* node_disk_reads_completed -> node_disk_reads_completed_total* node_disk_sectors_written -> node_disk_written_bytes_total* node_time -> node_time_seconds* node_boot_time -> node_boot_time_seconds* node_intr -> node_intr_total解决版本问题的方法有两种:一是在机器上启动两个版本的node-exporter,都让prometheus去采集。二是使用指标转换器,他会将旧指标名称转换为新指标对于grafana的展示,可以找同时支持两套指标的dashboard模板Collectornode-exporter的主函数:// Package collector includes all individual collectors to gather and export system metrics.package collectorimport ( “fmt” “sync” “time” “github.com/prometheus/client_golang/prometheus” “github.com/prometheus/common/log” “gopkg.in/alecthomas/kingpin.v2”)// Namespace defines the common namespace to be used by all metrics.const namespace = “node"可以看到exporter的实现需要引入github.com/prometheus/client_golang/prometheus库,client_golang是prometheus的官方go库,既可以用于集成现有应用,也可以作为连接Prometheus HTTP API的基础库。比如定义了基础的数据类型以及对应的方法:Counter:收集事件次数等单调递增的数据Gauge:收集当前的状态,比如数据库连接数Histogram:收集随机正态分布数据,比如响应延迟Summary:收集随机正态分布数据,和 Histogram 是类似的switch metricType { case dto.MetricType_COUNTER: valType = prometheus.CounterValue val = metric.Counter.GetValue() case dto.MetricType_GAUGE: valType = prometheus.GaugeValue val = metric.Gauge.GetValue() case dto.MetricType_UNTYPED: valType = prometheus.UntypedValue val = metric.Untyped.GetValue()client_golang库的详细解析可以参考:theory-source-code本文为容器监控实践系列文章,完整内容见:container-monitor-book ...

January 20, 2019 · 2 min · jiezi

与“十“俱进 阿里数据库运维10年演进之路

导语阿里巴巴集团拥有超大的数据库实例规模,在快速发展的过程中我们在运维管理方面也在不断的面临变化,从物理器到容器、从独占到混布、从本地盘到存储计算分离、从集团内到大促云资源,从开源的MySQL到自研分布式数据库,运维管控进行了自我革新与进化。作者—谭宇(花名:茂七),阿里巴巴高级技术专家。2009年加入阿里,对分布式系统和数据库领域有很大的兴趣。目前负责阿里巴巴集团数据库中台建设,支撑了集团数据库在容器化、存储计算分离、在离线混部、大规模迁移建站以及智能运维等技术探索与落地。本文梳理了阿里巴巴数据库运维发展历程以及对未来数据库自治时代的看法,期待与诸位一起讨论。正文我在阿里快十年了,前五年做一些分布式系统开发相关的工作,参与的系统包括TFS/Tair/OceanBase,后五年聚焦在数据库运维平台及服务的建设,从搭建数据库集群、数据采集等底层运维到外部客户服务、POC支持等都有所经历,好的想法历来稀少,外加个人天资愚钝,不敢说有什么独创的想法,都是实践过程中的一些感悟,且与大家分享,也是对过去一段时间的总结。关于阿里的数据库,大家可能已经听说过很多,阿里巴巴数据库技术的发展与整个集团的技术发展类似,从商业到开源再到自主研发。早在09年以前,阿里巴巴数据库或DBA团队已经在业界非常知名,拥有多名Oracle ACE / ACE Director,外加自发性的与业界的交流、分享以及著作,构建了非常强的影响力,很多人因此吸引而加入,这是一段荣耀时光,当时很多知名人物现在有的在创业、有的仍在集团不同的领域奋斗,江湖中仍然可以搜索到他们的传说。后来就是轰轰烈烈的去IOE运动了,刚入职时经常在内部BBS上看到各种成功去除Oracle的帖子,基本套路就是DBA和业务开发一起配合,通过各种脚本把数据迁移到MySQL上。在这个过程中,遇到过很多问题,也在积极寻求各种新的技术,比如为了突破磁盘性能问题,在当时主流的还是机械硬盘的时候,使用了Fusion-IO等,也在MySQL内核上开始各种改进,形成了AliSQL。关于去IOE、各自数据库内核技术、支撑的业务这些方面,讲的很多,大家可以搜到各自技术相关的文章,但很少有人讲过这背后有一个什么样的平台来支持这些业务变化以及技术迭代。过去的5年里,我和我的团队一直在做数据库运维或者是服务方面的事情,很难,我相信各位如果有做过这方面经验会感同身受。一方面,这是运维类或服务类系统的“原罪”,这类系统形成初期,它只是一个工具或一个平台,使命是支撑好业务,自己并不实际产生价值。所有的技术要在这里落地,等落完地好像和你又没什么关系,价值感比较弱。今天K8S等系统的出现说明这个也可以做得很好,但相对来说这是一个更难做好的领域。另一方面,业务的复杂性、技术栈的多样性以及所依赖的组件决定了这个系统在实现层面很难,你需要把各个组件一层一层的串联起来。从业务访问到数据库内核再到底层的网络、存储、OS、硬件等,任何一个层面出了问题都会集中反应到你的系统中,实现人员面临着从上到下各个层面问题的理解、异常的处理,对团队及个人能力要求极高。一个很具体的例子,我们的运维系统涉及了前端、大数据处理、算法、数据库、底层软硬件等各个技术领域。在最初期团队不可能有各个领域的专家,需要每个同学了解并解决不同的领域的问题。我想就这些方面,系统性地跟大家介绍一下我们所做的一些工作。主要包括三个方面:第一,我们整个系统的发展历程,所谓从历史看未来,不知道过去,无法推断出未来我们的样子。第二,现阶段的技术和产品,比如我们如何支撑我们现有的工作,双11大促等。第三,从过去和现在出发,我们未来一段时间要到达的样子。1. 从历史看未来阿里巴巴数据库运维发展的历程,主要有这几个阶段。09年以前,以商业数据库为主,去IOE也才开始。之前没有整体运维规划、运维平台。使用了各类脚本、工具。在09年以后,由于规模迅速膨胀,这个时候自然产生了一些工具团队,把各类脚本拼在一起,弄个界面,形成了最初的产品。接着,随着复杂度进一步增加,以及云计算的推动。交付方面有了更高的要求,形成了服务化,比如DBaaS等。近年来,随着大数据、机器学习等技术的发展,AIOPS催生智能化。在智能化的基础之上,对各类服务平台的服务质量的进一步要求,也就是自治。总体来讲,有两次比较大的革新。第一次是从产品化到服务化。最初,我们的产品形成非常简单。没有什么平台,没有什么系统,大家一边干活一边沉淀一些脚本,到处分发。随着规模的增长,原来的那套脚本需要管理起来了,我相信很多团队最开始都会设立一个工具组,专门来干这个事情。这就会演变成一个团队做工具,另一个团队来使用,慢慢的两者之间的GAP就出现了。工具团队有工具团队的KPI,业务团队有业务团队的KPI,分歧会逐渐增大。另外一个问题则是大家都在攒工具,攒平台。结果这些平台之间是相互不能通信的。比如一个应用开发,需要数据库、搜索、分布式存储等各个系统,开发人员需要去逐个申请,效率不高。服务化的变革就是在这种情况下发生的。我们不提供工具、平台,而提供服务。这些服务之间相互打通,并且我们对提供的服务有相关SLA保证。这次革新可以说是云计算的基础,云计算的本质是IT资源交付技术的进步,这是云计算带给我们的最大价值。第二次革新是自治,我们目前正处于这次革新中。对SLA或者服务质量的追求是没有止境的。现在很火的Cloud Native、Serverless本质上都是对更高服务质量的一种追求。要提升服务水平,关键点有两个,一是规模,规模大了才能做更多事情,比如混合部署、智能调度、机器学习,都要求一定的规模和大量的数据。这也符合当前提供基础服务的云计算趋于集中的趋势。规模也是问题的根源,管理一千个实例和十万个实例所需的技术完全不一样,所以另一个关键点是技术水平,有了规模还必须有相应的技术,比如容器化、机器学习、存储计算分离、RDMA网络等技术的提升。2. 理想照进现实当然技术的积累是一个长期的过程,积累到一定程度就会引起质变。我们这些年在系统建设、技术积累方面所做了许多工作。先来看一组数据,这是刚刚过去的双十一数据库相关的一些情况。大家可能看过一些数据,比如成交额,交易峰值。对于背后的数据库而言,每秒有超过5000万次的访问。特别是在高峰期,读写比是和平时不一样的。通常一般系统读写比大约是9:1或者7:1。但在双11高峰时,数据库的读写比可能是2:1。在写入比例极高的情况下,要求数据库不能出现任何抖动或者延迟,我们对任何一种新技术的引入都非常严格。另外,阿里巴巴大促场景非常复杂,包括线上线下以及海内外多种场景。基础设施分布在全球多地,数十个机房同时支撑,系统复杂性非常高。总结来看,我们的业务场景大致有以下几个特点:业务多样性。作为数据库中台,数据库团队要支撑集团所有业务。包括核心电商、线下新零售场景以及各个独立子公司。各种场景对数据库的要求也不一样,比如线上场景就要求高并发低延时,故障快速恢复。线下场景则要求绝对可用,而且接入及使用数据库的方式都不受控制。而新加入阿里体系的收购公司,有原来的运维体系,要求他们做改变也不太现实。总之需求的多样性对数据库的要求非常高。基础设施多样化,数据中心分布在全球,有的用公有云、有的有自建机房,有的用物理机,有的用Docker、用弹性计算等,整个集团就是一个超级大的混合云。双11超级大热点,除了成本和性能方面,双11在弹性方面有很高的要求。我们也不可能为双11买这么多机器,那太浪费了。早期,会在不同的业务线之间进行拆借,比如借云的机器或者借离线的机器,大促后归还,全靠人肉搬机器。整个大促周期非常长,人力成本和机器成本都很高。针对以上情况,我们形成了如下架构。主要思路是向下层屏蔽差异,向上层提供多样化服务能力,中间围绕着弹性、稳定性、智能化进行建设。早期的运维系统因为各种原因,没有设计好的架构导致难以扩展,最后越来越臃肿,推翻重构的事情并不少见。现今,我们设计了服务化的运维系统整体架构:一来可以清晰地理顺系统各个模块之间的交互方式并标准化,彻底解决原来工具时代遗留下来的各自为政,各个功能散落在不同地方的问题,整个系统的发展不再背负历史包袱;二来可以解决技术栈太多的问题,从前端到底层采集、算法分析,我们不可能统一成一个框架、一种语言。让这些模块能互不影响、互相交互,是整个系统能做强的基础;三来可以将整个系统的数据集中起来,为后续智能化所需要的统一数据、统一算法提供基本要素;四来可以向外提供统一形式、功能多样化的服务。要想做好做强一个服务类的系统,服务化是第一步。在底层,我们做了统一的资源调度层,用来屏蔽底层计算、存储、网络资源的差异,向上交付标准的容器。中间是数据库层。因为业务的多样性,数据库类型多种多样,运行在不同的环境,我们通过统一的命令通道和采集通道的抽象来屏蔽这些差异。再往上是传统的数据库运维层,包括常见的数据库的生命周期管理,我们称之为Lego,希望OPS这样的基础功能能像乐高一样百变组合,迅速搭建我们想要的功能。还包括数据采集、处理存储的模块Kepler,我们希望把所有的运行数据实时采集到,然后通过大数据处理、机器学习等手段发掘出深层价值,采集的数据包括OS、网络、存储,数据库的SQL流水、性能指标等等,整个处理的数据量非常大,并且所有指标都要求是秒级的。每秒都要处理超过100G的原始报文。再往上是智能决策层,这一层完成自动修复与优化的工作,比如预期内的故障,异常处理。也通过采集到的数据,做一些分析和决策。我们把整个系统的功能以服务的形式提供出来,大家可以在这个基础之上定制想要的功能。过去几年,我们在架构实现方面一直坚持“一个平台、一套架构,集团孵化、云上输出”的策略,集团内部数据库管控平台提供服务,所有模块在一套架构下实现,产品在集团检验后开始在云上输出。不同的时期有不同的坚持,在智能化时代,我们对这个架构及策略也有所调整,这个在后面会提及。解决架构问题后,我们过去两年主要围绕几个能力进行建设,一是容器化与存储计算分离,二是快速弹性与混部的能力,三是规模化交付与智能运维,这几项工作都是今天能够发展成为集团数据库中台以及支撑双十一的关键能力。第一,容器化是技术的量变到质变,容器并没有很多开创性的技术,各种技术合在一起开辟了新的思路。所以在把数据库放到容器里首先要突破我们的运维思路,坚定可以把数据库放到容器里的这个想法。当然这个过程中也遇到过稳定性和性能问题,比如网络在使用bridge模式的时候,会有些CPU的增加。最终在网络团队不断优化后,基本可以做到与物理机持平。容器化一方面解决了很多环境问题,另一方面也为数据库的调度提供了可能,我们从16年开始做数据库容器化,到今年,绝大部份的数据库都跑在了容器里。第二,存储计算分离。其实数据库最开始就是存储计算分离架构的。在互联网时代,这个架构在最初遇到了瓶颈,因为互联网时代的容量扩张非常快。然后出现了分布式系统,把计算搬到数据所在的地方。但是随着技术的发展,特别是云的交付方式,存储计算分离的交付则更为便捷。交付多少计算能力,交付多少存储,一目了然。另外,存储和计算的发展也不太均衡,比如双11的时候,我要求的是计算能力,其实存储并没有增加多少。所以随着技术的发展,我们这一圈基本上又转了回来。当然存储计算分离要不要上,我们也是经过了很长时间的讨论,今天来看,上存储计算分离这个决定是对的。在这个过程中也遇到很多问题,主要是延迟的问题,毕竟经过一层网络,IO时间是有增加的。我们最开始上了左边这个架构,将远程存储挂载到本地,这个延迟要较本地盘大很多,核心业务难以接受,在这个基础之上,我们大规模引入RDMA网络,通过DBFS bypass掉kernel,延时基本能和本地盘相当,所以今年所有的核心业务就跑在了这个架构上。有了容器化和存储计算分离的基础,就可以比较好的解决弹性问题了。之前我们的弹性主要靠搬数据加搬机器,现在数据可以不用搬了。我们大促所用的机器主要来自两个地方,一个是离线资源,另外一个是云资源,我们用完之后云可以继续对外售卖。大家知道,双11的高峰期主要在零点时段。所以我们在高峰期来的时候弹性扩容,高峰期结束立即缩容,还机器给别人用。我们结合集团的调度,做了一套混部调度系统,可以做到数据库快上快下,今年我们的核心集群10分钟就可以完成弹性扩缩,而我们最终的目标是在1分钟内完成。第三,交付和诊断。我们说云计算是IT资源交付能力的进步。我们基本完成了向用户交付数据库资源,开发人员可以通过系统自助完成数据库资源的申请与使用。如果只是把交付理解为用户自助获取数据库资源的话,我们已经完成得很好了。但集团有更严苛和复杂的交付任务。比如下面两个例子:大促时需要在上十万个数据库实例里扩容数千甚至上万个实例,大促完成后还需要缩容回来。每年有固定的或临时的建站、迁站等操作,例如今年的一路向北和上海、张北多次建站,可能会涉及到数万数据库实例及数十PB数据,这些都非常考验我们交付的能力。之前的常规做法是让人来评估,确定好操作的数据库范围,算好需要多少机器资源,如何摆放等,再通过我们提供的迁移操作来完成。人需要来控制其中的每一个步骤,这是一个相当繁琐的工作。每年都要做那么几次,在我们今天要求快上快下的时候就显得特别力不从心。所以我们提出软件定义部署的概念,类似常说的编排。主要做两个事情,一是把我们的这些操作都精确定义和记载下来,线上的运行环境也能用代码精确定义出来。二是把中间的腾挪步骤交给系统,人只需要描述最终的状态,在我们预测准确到一定程度后,这个最终描述状态也可以由系统来完成,真正的完成大促自动化交付。可以看到,我们的链路其实很长,中间的各个组件出了问题诊断是一件很难的事情。Gartner有一个名词叫AIOPS,相信大家都听说过,其实Gartner提出AIOPS很早,最开始指的是基于算法的OPS,随着AI技术的发展呢,他顺势把这个词的写法给改了,但本质上没有变,仍然是依托大数据、算法来改变OPS。应该说这也是个朴素的概念,我们在差不多时刻也提出了这个想法,最开始叫做数据驱动,后来改名为运行数据与数据运营,也是通过各种算法,比如基线、异常检测、关联分析、趋势预测等等来自动决策或辅助我们决策。这便是CloudDBA的初衷,先把各种采集到的数据汇聚统一、预处理再加上领域知识和算法,打通从用户到DB,从DB到底层硬件这两个链路。在双11的时候也能实时的诊断访问链路。当然这里待挖掘的还非常多,现在我们可以说只应用了非常小的一部份。最后,我把之前讲的这些都融进了一个产品HDM,全称是混合云数据库管理平台。Gartner有一份报告指出,到2020年的时候,有85%的企业会使用混合云的架构。这和我们的判断是一致的。之前提到过,阿里巴巴集团就是一个超大的混合云,所以我们推出了HDM,帮助企业把混合云数据库架构打通。HDM主要提供两大功能,一是统一管理,不管是云上的还是云下还是其他云的数据库,都可以进行统一管理与诊断。二是弹性扩展,一键把数据库上云也不再是梦想。在数据库层消除本地IDC和云的区别。在提出HDM后一段时间里,我一度认为这基本上就是数据库服务的未来了。但这些年,不管是数据库领域,还是运维领域,都在飞速的向前发展,我们似乎又到了一个技术集中爆发的时间点。一方面是新的软硬件技术,比如容器、高速网络,机器学习,另外一个是云计算的发展,都在不断的推动我们向前,提供更好的交付及服务。3. 通往智能之路:自治数据库平台在数据库领域,有自治数据库、智能数据库,在运维领域,有AIOPS等等。这对数据库运维来说到底意味着什么,我们结合过去和现在的情况,提出了自治数据库平台。这个定义是很多人一起讨论了很久才定出来的。首先是平台的能力和目标,我们希望能做到自动驾驶,简单的说就是不需要人的参与。要做到这个,就要具备自感知、自决策、自恢复、自优化四大能力。其次是平台能为数据库赋能,今天的现状是我们用了很多种数据库,不能对每个数据库有很高的要求才能自治,我们可以进行能力分级。我们也有非常明确的业务目标,这是阿里数据库事业部掌门人公开的目标:在2020财年结束的时候,阿里集团85%的数据库实例要能做到自动驾驶。我为此定了两个评估指标,一是没有人接收这些数据库的报警,另一个是稳定性要达到99.995%。目标有了,具体怎么实现?首先,自治是一个技术的量变到质变的过程。在过去的一段时间里,我们应用了大量的新技术,包括存储计算分离,大数据处理与机器学习等等,掌握好这些技术是自治的基础。有了这些技术,还需要革新我们的思路,就像今天的电动汽车或自动驾驶,由传统厂商来制造,都显得差强人意,这一方面是思维定势,另一方面则是有它的历史包袱。我们需要破除这些,比如我们之前的数据、运维、资源都是分割开来的,所谓自动处理都是先接收一个报警,然后根据报警的内容做自动化,这明显没办法形成一个统一的整体。今天我们需要先去构建整个骨架,然后让数据、算法去丰富、去润滑整个系统。另外还需要专业知识。数据库是高度专业的系统,之前可能由DBA、内核开发人员去调校,靠数据,靠试错,靠经验。这些知识如何精确化、数字化下来,让系统也具备这些能力,是我们要去努力的事情。最后,重要的一点是要持续提升原有的基础能力,不是说我们今天智能化、自治化就是数据和算法,其他基础能力同等重要,比如OPS,我们提出了一个ad-hoc执行:假如决策模块作出了一个决策是全网内存水位高于95%的数据库实例做一个action,你要能够很快执行下去。我们目前正在向这个方向前进,预计很快我们就会对一部份数据库实例实施自治,欢迎有兴趣的同学加入一起建设,共同迎接自治时代的到来。本文作者:七幕阅读原文本文为云栖社区原创内容,未经允许不得转载。

January 14, 2019 · 1 min · jiezi

容器监控实践—Custom Metrics

概述上文metric-server提到,kubernetes的监控指标分为两种:Core metrics(核心指标):从 Kubelet、cAdvisor 等获取度量数据,再由metrics-server提供给 Dashboard、HPA 控制器等使用。Custom Metrics(自定义指标):由Prometheus Adapter提供API custom.metrics.k8s.io,由此可支持任意Prometheus采集到的指标。核心指标只包含node和pod的cpu、内存等,一般来说,核心指标作HPA已经足够,但如果想根据自定义指标:如请求qps/5xx错误数来实现HPA,就需要使用自定义指标了,目前Kubernetes中自定义指标一般由Prometheus来提供,再利用k8s-prometheus-adpater聚合到apiserver,实现和核心指标(metric-server)同样的效果。以下是官方metrics的项目介绍:Resource Metrics API(核心api)HeapsterMetrics ServerCustom Metrics API:Prometheus AdapterMicrosoft Azure AdapterGoogle StackdriverDatadog Cluster Agent部署Prometheus可以采集其它各种指标,但是prometheus采集到的metrics并不能直接给k8s用,因为两者数据格式不兼容,因此还需要另外一个组件(kube-state-metrics),将prometheus的metrics数据格式转换成k8s API接口能识别的格式,转换以后,因为是自定义API,所以还需要用Kubernetes aggregator在主API服务器中注册,以便直接通过/apis/来访问。文件清单:node-exporter:prometheus的export,收集Node级别的监控数据prometheus:监控服务端,从node-exporter拉数据并存储为时序数据。kube-state-metrics:将prometheus中可以用PromQL查询到的指标数据转换成k8s对应的数k8s-prometheus-adpater:聚合进apiserver,即一种custom-metrics-apiserver实现开启Kubernetes aggregator功能(参考上文metric-server)k8s-prometheus-adapter的部署文件:其中创建了一个叫做cm-adapter-serving-certs的secret,包含两个值: serving.crt和serving.key,这是由apiserver信任的证书。kube-prometheus项目中的gencerts.sh和deploy.sh脚本可以创建这个secret包括secret的所有资源,都在custom-metrics命名空间下,因此需要kubectl create namespace custom-metrics以上组件均部署成功后,可以通过url获取指标基于自定义指标的HPA使用prometheus后,pod有一些自定义指标,如http_request请求数创建一个HPA,当请求数超过每秒10次时进行自动扩容apiVersion: autoscaling/v2beta1kind: HorizontalPodAutoscalermetadata: name: podinfospec: scaleTargetRef: apiVersion: extensions/v1beta1 kind: Deployment name: podinfo minReplicas: 2 maxReplicas: 10 metrics: - type: Pods pods: metricName: http_requests targetAverageValue: 10查看hpa$ kubectl get hpaNAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGEpodinfo Deployment/podinfo 899m / 10 2 10 2 1m对pod进行施压#install hey$ go get -u github.com/rakyll/hey#do 10K requests rate limited at 25 QPS$ hey -n 10000 -q 5 -c 5 http://PODINFO_SVC_IP:9898/healthzHPA发挥作用:Events: Type Reason Age From Message —- —— —- —- ——- Normal SuccessfulRescale 5m horizontal-pod-autoscaler New size: 3; reason: pods metric http_requests above target Normal SuccessfulRescale 21s horizontal-pod-autoscaler New size: 2; reason: All metrics below target关于k8s-prometheus-adapter其实k8s-prometheus-adapter既包含自定义指标,又包含核心指标,即如果按照了prometheus,且指标都采集完整,k8s-prometheus-adapter可以替代metrics server。在1.6以上的集群中,k8s-prometheus-adapter可以适配autoscaling/v2的HPA因为一般是部署在集群内,所以k8s-prometheus-adapter默认情况下,使用in-cluster的认证方式,以下是主要参数:lister-kubeconfig: 默认使用in-cluster方式metrics-relist-interval: 更新metric缓存值的间隔,最好大于等于Prometheus 的scrape interval,不然数据会为空prometheus-url: 对应连接的prometheus地址config: 一个yaml文件,配置如何从prometheus获取数据,并与k8s的资源做对应,以及如何在api接口中展示。config文件的内容示例(参考文档)rules: - seriesQuery: ‘{name="^container_.",container_name!=“POD”,namespace!="",pod_name!=""}’ seriesFilters: [] resources: overrides: namespace: resource: namespace pod_name: resource: pod name: matches: ^container_(.)_seconds_total$ as: "" metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>,container_name!=“POD”}[1m])) by (<<.GroupBy>>) - seriesQuery: ‘{name="^container_.",container_name!=“POD”,namespace!="",pod_name!=""}’ seriesFilters: - isNot: ^container_.seconds_total$ resources: overrides: namespace: resource: namespace pod_name: resource: pod name: matches: ^container(.)total$ as: "" metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>,container_name!=“POD”}[1m])) by (<<.GroupBy>>) - seriesQuery: ‘{name=~"^container.",container_name!=“POD”,namespace!="",pod_name!=""}’ seriesFilters: - isNot: ^container_.total$ resources: overrides: namespace: resource: namespace pod_name: resource: pod name: matches: ^container(.)$ as: "" metricsQuery: sum(<<.Series>>{<<.LabelMatchers>>,container_name!=“POD”}) by (<<.GroupBy>>)问题为什么我看不到自定义的metric检查下config配置文件,是否有选择你的metric检查下采集的信息是否正确,如foo{namespace=“somens”,deployment=“bar”},foo这个名称的数据来自于somens的命名空间+bar这个部署启动的时候加上–v=6,可以打出adapter实际的query信息参考k8s-prometheus-adapter,可以实现自己的adapter,比如获取已有监控系统的指标,汇聚到api-server中,k8s-prometheus-adapter的实现逻辑会在后续文章中专门来讲。本文为容器监控实践系列文章,完整内容见:container-monitor-book ...

January 13, 2019 · 1 min · jiezi

容器监控实践—kube-state-metrics

概述已经有了cadvisor、heapster、metric-server,几乎容器运行的所有指标都能拿到,但是下面这种情况却无能为力:我调度了多少个replicas?现在可用的有几个?多少个Pod是running/stopped/terminated状态?Pod重启了多少次?我有多少job在运行中而这些则是kube-state-metrics提供的内容,它基于client-go开发,轮询Kubernetes API,并将Kubernetes的结构化信息转换为metrics。功能kube-state-metrics提供的指标,按照阶段分为三种类别:1.实验性质的:k8s api中alpha阶段的或者spec的字段。2.稳定版本的:k8s中不向后兼容的主要版本的更新3.被废弃的:已经不在维护的。指标类别包括:CronJob MetricsDaemonSet MetricsDeployment MetricsJob MetricsLimitRange MetricsNode MetricsPersistentVolume MetricsPersistentVolumeClaim MetricsPod MetricsPod Disruption Budget MetricsReplicaSet MetricsReplicationController MetricsResourceQuota MetricsService MetricsStatefulSet MetricsNamespace MetricsHorizontal Pod Autoscaler MetricsEndpoint MetricsSecret MetricsConfigMap Metrics以pod为例:kube_pod_infokube_pod_ownerkube_pod_status_phasekube_pod_status_readykube_pod_status_scheduledkube_pod_container_status_waitingkube_pod_container_status_terminated_reason…使用部署清单: kube-state-metrics/ ├── kube-state-metrics-cluster-role-binding.yaml ├── kube-state-metrics-cluster-role.yaml ├── kube-state-metrics-deployment.yaml ├── kube-state-metrics-role-binding.yaml ├── kube-state-metrics-role.yaml ├── kube-state-metrics-service-account.yaml ├── kube-state-metrics-service.yaml主要镜像有:image: quay.io/coreos/kube-state-metrics:v1.5.0image: k8s.gcr.io/addon-resizer:1.8.3(参考metric-server文章,用于扩缩容)对于pod的资源限制,一般情况下:200MiB memory0.1 cores超过100节点的集群:2MiB memory per node0.001 cores per nodekube-state-metrics做过一次性能优化,具体内容参考下文部署成功后,prometheus的target会出现如下标志因为kube-state-metrics-service.yaml中有prometheus.io/scrape: ’true’标识,因此会将metric暴露给prometheus,而Prometheus会在kubernetes-service-endpoints这个job下自动发现kube-state-metrics,并开始拉取metrics,无需其他配置。使用kube-state-metrics后的常用场景有:存在执行失败的Job: kube_job_status_failed{job=“kubernetes-service-endpoints”,k8s_app=“kube-state-metrics”}==1集群节点状态错误: kube_node_status_condition{condition=“Ready”,status!=“true”}==1集群中存在启动失败的Pod:kube_pod_status_phase{phase=~“Failed|Unknown”}==1最近30分钟内有Pod容器重启: changes(kube_pod_container_status_restarts[30m])>0配合报警可以更好地监控集群的运行与metric-server的对比metric-server(或heapster)是从api-server中获取cpu、内存使用率这种监控指标,并把他们发送给存储后端,如influxdb或云厂商,他当前的核心作用是:为HPA等组件提供决策指标支持。kube-state-metrics关注于获取k8s各种资源的最新状态,如deployment或者daemonset,之所以没有把kube-state-metrics纳入到metric-server的能力中,是因为他们的关注点本质上是不一样的。metric-server仅仅是获取、格式化现有数据,写入特定的存储,实质上是一个监控系统。而kube-state-metrics是将k8s的运行状况在内存中做了个快照,并且获取新的指标,但他没有能力导出这些指标换个角度讲,kube-state-metrics本身是metric-server的一种数据来源,虽然现在没有这么做。另外,像Prometheus这种监控系统,并不会去用metric-server中的数据,他都是自己做指标收集、集成的(Prometheus包含了metric-server的能力),但Prometheus可以监控metric-server本身组件的监控状态并适时报警,这里的监控就可以通过kube-state-metrics来实现,如metric-serverpod的运行状态。深入解析kube-state-metrics本质上是不断轮询api-server,代码结构也很简单主要代码目录.├── collectors│ ├── builder.go│ ├── collectors.go│ ├── configmap.go│ ……│ ├── testutils.go│ ├── testutils_test.go│ └── utils.go├── constant│ └── resource_unit.go├── metrics│ ├── metrics.go│ └── metrics_test.go├── metrics_store│ ├── metrics_store.go│ └── metrics_store_test.go├── options│ ├── collector.go│ ├── options.go│ ├── options_test.go│ ├── types.go│ └── types_test.go├── version│ └── version.go└── whiteblacklist ├── whiteblacklist.go └── whiteblacklist_test.go所有类型:var ( DefaultNamespaces = NamespaceList{metav1.NamespaceAll} DefaultCollectors = CollectorSet{ “daemonsets”: struct{}{}, “deployments”: struct{}{}, “limitranges”: struct{}{}, “nodes”: struct{}{}, “pods”: struct{}{}, “poddisruptionbudgets”: struct{}{}, “replicasets”: struct{}{}, “replicationcontrollers”: struct{}{}, “resourcequotas”: struct{}{}, “services”: struct{}{}, “jobs”: struct{}{}, “cronjobs”: struct{}{}, “statefulsets”: struct{}{}, “persistentvolumes”: struct{}{}, “persistentvolumeclaims”: struct{}{}, “namespaces”: struct{}{}, “horizontalpodautoscalers”: struct{}{}, “endpoints”: struct{}{}, “secrets”: struct{}{}, “configmaps”: struct{}{}, })构建对应的收集器Family即一个类型的资源集合,如job下的kube_job_info、kube_job_created,都是一个FamilyGenerator实例metrics.FamilyGenerator{ Name: “kube_job_info”, Type: metrics.MetricTypeGauge, Help: “Information about job.”, GenerateFunc: wrapJobFunc(func(j *v1batch.Job) metrics.Family { return metrics.Family{&metrics.Metric{ Name: “kube_job_info”, Value: 1, }} }), },func (b *Builder) buildCronJobCollector() *Collector { // 过滤传入的白名单 filteredMetricFamilies := filterMetricFamilies(b.whiteBlackList, cronJobMetricFamilies) composedMetricGenFuncs := composeMetricGenFuncs(filteredMetricFamilies) // 将参数写到header中 familyHeaders := extractMetricFamilyHeaders(filteredMetricFamilies) // NewMetricsStore实现了client-go的cache.Store接口,实现本地缓存。 store := metricsstore.NewMetricsStore( familyHeaders, composedMetricGenFuncs, ) // 按namespace构建Reflector,监听变化 reflectorPerNamespace(b.ctx, b.kubeClient, &batchv1beta1.CronJob{}, store, b.namespaces, createCronJobListWatch) return NewCollector(store)}性能优化:kube-state-metrics在之前的版本中暴露出两个问题:/metrics接口响应慢(10-20s)内存消耗太大,导致超出limit被杀掉问题一的方案就是基于client-go的cache tool实现本地缓存,具体结构为:var cache = map[uuid][]byte{}问题二的的方案是:对于时间序列的字符串,是存在很多重复字符的(如namespace等前缀筛选),可以用指针或者结构化这些重复字符。优化点和问题1.因为kube-state-metrics是监听资源的add、delete、update事件,那么在kube-state-metrics部署之前已经运行的资源,岂不是拿不到数据?kube-state-metric利用client-go可以初始化所有已经存在的资源对象,确保没有任何遗漏2.kube-state-metrics当前不会输出metadata信息(如help和description)3.缓存实现是基于golang的map,解决并发读问题当期是用了一个简单的互斥锁,应该可以解决问题,后续会考虑golang的sync.Map安全map。4.kube-state-metrics通过比较resource version来保证event的顺序5.kube-state-metrics并不保证包含所有资源本文为容器监控实践系列文章,完整内容见:container-monitor-book ...

January 13, 2019 · 2 min · jiezi

最小化 Java 镜像的常用技巧

背景随着容器技术的普及,越来越多的应用被容器化。人们使用容器的频率越来越高,但常常忽略一个基本但又非常重要的问题 - 容器镜像的体积。本文将介绍精简容器镜像的必要性并以基于 spring boot 的 java 应用为例描述最小化容器镜像的常用技巧。精简容器镜像的必要性精简容器镜像是非常必要的,下面分别从安全性和敏捷性两个角度进行阐释。安全性基于安全方面的考虑,将不必要的组件从镜像中移除可以减少攻击面、降低安全风险。虽然 docker 支持用户通过 Seccomp 限制容器内可以执行操作或者使用 AppArmor 为容器配置安全策略,但它们的使用门槛较高,要求用户具备安全领域的专业素养。敏捷性精简的容器镜像能提高容器的部署速度。假设某一时刻访问流量激增,您需要通过增加容器副本数以应对突发压力。如果某些宿主机不包含目标镜像,需要先拉取镜像,然后启动容器,这时使用体积较小的镜像能加速这一过程、缩短扩容时间。另外,镜像体积越小,其构建速度也越快,同时还能减少存储和传输的成本。常用技巧将一个 java 应用容器化所需的步骤可归纳如下:编译 java 源码并生成 jar 包。将应用 jar 包和依赖的第三方 jar 包移动到合适的位置。本章所用的样例是一个基于 spring boot 的 java 应用 spring-boot-docker,所用的未经优化的 dockerfile 如下:FROM maven:3.5-jdk-8COPY src /usr/src/app/srcCOPY pom.xml /usr/src/appRUN mvn -f /usr/src/app/pom.xml clean packageENTRYPOINT [“java”,"-jar","/usr/src/app/target/spring-boot-docker-1.0.0.jar"]由于应用使用 maven 构建,dockerfile 中指定maven:3.5-jdk-8作为基础镜像,该镜像的大小为 635MB。通过这种方式最终构建出的镜像非常大,达到了 719MB,这是因为一方面基础镜像本身就很大,另一方面 maven 在构建过程中会下载许多用于执行构建任务的 jar 包。多阶段构建Java 程序的运行只依赖 JRE,并不需要 maven 或者 JDK 中众多用于编译、调试、运行的工具,因此一个明显的优化方法是将用于编译构建 java 源码的镜像和用于运行 java 应用的镜像分开。为了达到这一目的,在 docker 17.05 版本之前需要用户维护 2 个 dockerfile 文件,这无疑增加了构建的复杂性。好在自 17.05 开始,docker 引入了多阶段构建的概念,它允许用户在一个 dockerfile 中使用多个 From 语句。每个 From 语句可以指定不同的基础镜像并将开启一个全新的构建流程。您可以选择性地将前一阶段的构建产物复制到另一个阶段,从而只将必要的内容保留在最终的镜像里。优化后的 dockerfile 如下:FROM maven:3.5-jdk-8 AS buildCOPY src /usr/src/app/srcCOPY pom.xml /usr/src/appRUN mvn -f /usr/src/app/pom.xml clean packageFROM openjdk:8-jreARG DEPENDENCY=/usr/src/app/target/dependencyCOPY –from=build ${DEPENDENCY}/BOOT-INF/lib /app/libCOPY –from=build ${DEPENDENCY}/META-INF /app/META-INFCOPY –from=build ${DEPENDENCY}/BOOT-INF/classes /appENTRYPOINT [“java”,"-cp",“app:app/lib/”,“hello.Application”]该 dockerfile 选用maven:3.5-jdk-8作为第一阶段的构建镜像,选用openjdk:8-jre作为运行 java 应用的基础镜像并且只拷贝了第一阶段编译好的.claass文件和依赖的第三方 jar 包到最终的镜像里。通过这种方式优化后的镜像大小为 459MB。使用 distroless 作为基础镜像虽然通过多阶段构建能减小最终生成的镜像的大小,但 459MB 的体积仍相对过大。经调查发现,这是因为使用的基础镜像openjdk:8-jre体积过大,到达了 443MB,因此下一步的优化方向是减小基础镜像的体积。Google 开源的项目 distroless 正是为了解决基础镜像体积过大这一问题。Distroless 镜像只包含应用程序及其运行时依赖项,不包含包管理器、shell 以及在标准 Linux 发行版中可以找到的任何其他程序。目前,distroless 为依赖 java、python、nodejs、dotnet 等环境的应用提供了基础镜像。使用 distroless 的 dockerfile 如下:FROM maven:3.5-jdk-8 AS buildCOPY src /usr/src/app/srcCOPY pom.xml /usr/src/appRUN mvn -f /usr/src/app/pom.xml clean packageFROM gcr.io/distroless/javaARG DEPENDENCY=/usr/src/app/target/dependencyCOPY –from=build ${DEPENDENCY}/BOOT-INF/lib /app/libCOPY –from=build ${DEPENDENCY}/META-INF /app/META-INFCOPY –from=build ${DEPENDENCY}/BOOT-INF/classes /appENTRYPOINT [“java”,"-cp",“app:app/lib/”,“hello.Application”]该 dockerfile 和上一版的唯一区别在于将运行阶段依赖的基础镜像由openjdk:8-jre(443 MB)替换成了gcr.io/distroless/java(119 MB)。经过这一优化,最终镜像的大小为 135MB。使用 distroless 的唯一不便是您无法 attach 到一个正在运行的容器上排查问题,因为镜像中不包含 shell。虽然 distroless 的 debug 镜像提供 busybox shell,但需要用户重新打包镜像、部署容器,对于那些已经基于非 debug 镜像部署的容器无济于事。 但从安全角度来看,无法 attach 容器并不完全是坏事,因为攻击者无法通过 shell 进行攻击。使用 alpine 作为基础镜像如果您确实有 attach 容器的需求,又希望最小化镜像的大小,可以选用 alpine 作为基础镜像。Alpine 镜像的特点是体积非常下,基础款镜像的体积仅 4 MB 左右。使用 alpine 后的 dockerfile 如下:FROM maven:3.5-jdk-8 AS buildCOPY src /usr/src/app/srcCOPY pom.xml /usr/src/appRUN mvn -f /usr/src/app/pom.xml clean packageFROM openjdk:8-jre-alpineARG DEPENDENCY=/usr/src/app/target/dependencyCOPY –from=build ${DEPENDENCY}/BOOT-INF/lib /app/libCOPY –from=build ${DEPENDENCY}/META-INF /app/META-INFCOPY –from=build ${DEPENDENCY}/BOOT-INF/classes /appENTRYPOINT [“java”,"-cp",“app:app/lib/*”,“hello.Application”]这里并未直接继承基础款 alpine,而是选用从 alpine 构建出的包含 java 运行时的openjdk:8-jre-alpine(83MB)作为基础镜像。使用该 dockerfile 构建出的镜像体积为 99.2MB,比基于 distroless 的还要小。执行命令docker exec -ti <container_id> sh可以成功 attach 到运行的容器中。distroless vs alpine既然 distroless 和 alpine 都能提供非常小的基础镜像,那么在生产环境中到底应该选择哪一种呢?如果安全性是您的首要考虑因素,建议选用 distroless,因为它唯一可运行的二进制文件就是您打包的应用;如果您更关注镜像的体积,可以选用 alpine。其他技巧除了可以通过上述技巧精简镜像外,还有以下方式:将 dockerfile 中的多条指令合并成一条,通过减少镜像层数的方式达到精简镜像体积的目的。将稳定且体积较大的内容置于镜像下层,将变动频繁且体积较小的内容置于镜像上层。虽然该方式无法直接精简镜像体积,但充分利用了镜像的缓存机制,同样可以达到加快镜像构建和容器部署的目的。想了解更多优化 dockerfile 的小窍门可参考教程 Best practices for writing Dockerfiles。总结本文通过一系列的优化,将 java 应用的镜像体积由最初的 719MB 缩小到 100MB 左右。如果您的应用依赖其他环境,也可以用类似的原则进行优化。针对 java 镜像,google 提供的另一款工具 jib 能为您屏蔽镜像构建过程中的复杂细节,自动构建出精简的 java 镜像。使用它您无须编写 dockerfile,甚至不需要安装 docker。对于类似 distroless 这样无法 attach 或者不方便 attach 的容器,建议您将它们的日志中心化存储,以便问题的追踪和排查。具体方法可参考文章面向容器日志的技术实践。本文作者:吴波bruce_wu阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

January 11, 2019 · 2 min · jiezi

Kubernetes API 与 Operator,不为人知的开发者战争

如果我问你,如何把一个 etcd 集群部署在 Google Cloud 或者阿里云上,你一定会不假思索的给出答案:当然是用 etcd Operator!实际上,几乎在一夜之间,Kubernetes Operator 这个新生事物,就成了开发和部署分布式应用的一项事实标准。时至今日,无论是 etcd、TiDB、Redis,还是 Kafka、RocketMQ、Spark、TensorFlow,几乎每一个你能叫上名字来的分布式项目,都由官方维护着各自的 Kubernetes Operator。而 Operator 官方库里,也一直维护着一个知名分布式项目的 Operator 汇总。https://github.com/operator-framework/awesome-operators短短一年多时间,这个列表的长度已经增长了几十倍。 而且更有意思的是,如果你仔细翻阅这个 Operator 列表,你就不难发现这样一个有趣的事实:现今 Kubernetes Operator 的意义,恐怕已经远远超过了“分布式应用部署”的这个原始的范畴,而已然成为了容器化时代应用开发与发布的一个全新途径。所以,你才会在这个列表里看到,Android SDK 的开发者们,正在使用 Operator “一键”生成和更新 Android 开发环境;而 Linux 系统工程师们,则在使用Operator “一键”重现性能测试集群。如果说,Docker 镜像的提出,完成了应用静态描述的标准化。那么 Kubernetes Operator 的出现,终于为应用的动态描述提出了一套行之有效的实现规范。更为重要的是,对于 TiDB、Kafka、RocketMQ 等分布式应用的开发者来说,这些应用运行起来之后的动态描述,才是对一个分布式应用真正有意义的信息。而在此之前,用户如果要想将 TiDB、Kafka 这样的分布式应用很好的使用起来,就不得不去尝试编写一套复杂的管理脚本,甚至为此学习大量与项目本身无关的运维知识。更为麻烦的是,这些脚本、知识、和经验,并没有一个很好的办法能够有效的沉淀下来。而任何一种技术的传授,如果严重依赖于口口相传而不是固化的代码和逻辑的话,那么它的维护成本和使用门槛,就可以说是“灾难级”的。所以说,Kubernetes Operator 发布之初最大的意义,就在于它将分布式应用的使用门槛直接降到了最低。那么这个门槛具体有多低呢?一般来说,无论这个分布式应用项目有多复杂,只要它为用户提供了 Operator,那么这个项目的使用就只需要两条命令即可搞定,以 Kafka 为例:这两条命令执行完成后,一个 Kafka 集群运行所需的节点,以及它们所依赖的 ZooKeeper 节点,就会以容器的方式自动出现在你的 Kubernetes 集群里了。不过,简化运维和部署,其实只是 Operator 在用户层面的表象。而在更底层的技术层面,Operator 最大的价值,在于它为“容器究竟能不能管理有状态应用”这个颇具争议话题,画上了一个优雅的句号。要知道,在2014-2015年的时候,伴随着 Docker 公司和 Docker 项目的走红,整个云计算生态几乎都陷入了名为“容器”的狂热当中。然而,相比于 “容器化”浪潮的如火如荼,这个圈子却始终对“有状态应用”讳莫如深。事实上,有状态应用(比如, 前面提到的Kafka )跟无状态应用(比如,一个简单的Jave Web网站)的不同之处,就在于前者对某些外部资源有着绑定性的依赖,比如远程存储,或者网络设备,以及,有状态应用的多个示例之间往往有着拓扑关系。这两种设计,在软件工程的世界里可以说再普通不过了,而且我们几乎可以下这样一个结论:所有的分布式应用都是有状态应用。但是,在容器的世界里,分布式应用却成了一个“异类”。我们知道,容器的本质,其实就是一个被限制了“世界观”的进程。在这种隔离和限制的大基调下,容器技术本身的“人格基因”,就是对外部世界(即:宿主机)的“视而不见”和“充耳不闻”。所以我们经常说,容器的“状态”一定是“易失”的。其实,容器对它的“小世界”之外的状态和数据漠不关心,正是这种“隔离性”的主要体现。但状态“易失”并不能说是容器的缺陷:我们既然对容器可以重现完整的应用执行环境的“一致性”拍手称赞,那就必然要对这种能力背后的限制了然于心。这种默契,也正是早期的 Docker 公司所向披靡的重要背景:在这个阶段,相比于“容器化”的巨大吸引力,开发者是可以暂时接受一部分应用不能运行在容器里的。而分布式应用容器化的困境,其实就在于它成为了这种“容器化”默契的“终极破坏者”。一个应用本身可以拥有多个可扩展的实例,这本来是容器化应用令人津津乐道的一个优势。但是一旦这些实例像分布式应用这样具有了拓扑关系,以及,这些实例本身不完全等价的时候,容器化的解决方案就再次变得“丑陋”起来:这种情况下,应用开发者们不仅又要为这些容器实例编写一套难以维护的管理脚本,还必须要想办法应对容器重启后状态丢失的难题。而这些容器状态的维护,实际上往往需要打破容器的隔离性、让容器对外部世界有所感知才能做到,这就使得容器化与有状态,成为了两种完全相悖的需求。不过,从上面的叙述中相信你也应该已经察觉到,分布式应用容器化的难点,并不在于容器本身有什么重大缺陷,而在于我们一直以来缺乏一种对“状态”的合理的抽象与描述,使得状态可以和容器进程本身解耦开来。这也就解释了为什么,在 Kubernetes 这样的外部编排框架逐渐成熟起了之后,业界才逐渐对有状态应用管理开始有了比较清晰的理解和认识。而我们知道, Kubernetes 项目最具价值的理念,就是它围绕 etcd 构建出来的一套“面向终态”编排体系,这套体系在开源社区里,就是大名鼎鼎的“声明式 API”。“声明式 API”的核心原理,就是当用户向 Kubernetes 提交了一个 API 对象的描述之后,Kubernetes 会负责为你保证整个集群里各项资源的状态,都与你的 API 对象描述的需求相一致。更重要的是,这个保证是一项“无条件的”、“没有期限”的承诺:对于每个保存在 etcd 里的 API 对象,Kubernetes 都通过启动一种叫做“控制器模式”(Controller Pattern)的无限循环,不断检查,然后调谐,最后确保整个集群的状态与这个 API 对象的描述一致。比如,你提交的 API 对象是一个应用,描述的是这个应用必须有三个实例,那么无论接下来你的 API 对象发生任何“风吹草动”,控制器都会检查一遍这个集群里是不是真的有三个应用实例在运行。并且,它会根据这次检查的结果来决定,是不是需要对集群做某些操作来完成这次“调谐”过程。当然,这里控制器正是依靠 etcd 的 Watch API 来实现对 API 对象变化的感知的。在整个过程中,你提交的 API 对象就是 Kubernetes 控制器眼中的“金科玉律”,是接下来控制器执行调谐逻辑要达到的唯一状态。这就是我们所说的“终态”的含义。而 Operator 的设计,其实就是把这个“控制器”模式的思想,贯彻的更加彻底。在 Operator 里,你提交的 API 对象不再是一个单体应用的描述,而是一个完整的分布式应用集群的描述。这里的区别在于,整个分布式应用集群的状态和定义,都成了Kubernetes 控制器需要保证的“终态”。比如,这个应用有几个实例,实例间的关系如何处理,实例需要把数据存储在哪里,如何对实例数据进行备份和恢复,都是这个控制器需要根据 API 对象的变化进行处理的逻辑。从上述叙述中,你就应该能够明白, Operator 其实就是一段代码,这段代码 Watch 了 etcd 里一个描述分布式应用集群的API 对象,然后这段代码通过实现 Kubernetes 的控制器模式,来保证这个集群始终跟用户的定义完全相同。而在这个过程中,Operator 也有能力利用 Kubernetes 的存储、网络插件等外部资源,协同的为应用状态的保持提供帮助。所以说,Operator 本身在实现上,其实是在 Kubernetes 声明式 API 基础上的一种“微创新”。它合理的利用了 Kubernetes API 可以添加自定义 API 类型的能力,然后又巧妙的通过 Kubernetes 原生的“控制器模式”,完成了一个面向分布式应用终态的调谐过程。而 Operator 本身在用法上,则是一个需要用户大量编写代码的的开发者工具。 不过,这个编写代码的过程,并没有像很多人当初料想的那样导致 Operator 项目走向小众,反而在短短三年的时间里, Operator 就迅速成为了容器化分布式应用管理的事实标准。时至今日,Operator 项目的生态地位已经毋庸置疑。就在刚刚结束的2018年 KubeCon 北美峰会上,Operator 项目和大量的用户案例一次又一次出现在聚光灯前,不断的印证着这个小小的“微创新”对整个云计算社区所产生的深远影响。不过,在 Operator 项目引人瞩目的成长经历背后,你是否考虑过这样一个问题:Kubernetes 项目一直以来,其实都内置着一个管理有状态应用的能力叫作 StatefulSet。而如果你稍微了解 Kubernetes 项目的话就不难发现,Operator 和 StatefulSet,虽然在对应用状态的抽象上有所不同,但它们的设计原理,几乎是完全一致的,即:这两种机制的本质,都是围绕Kubernetes API 对象的“终态”进行调谐的一个控制器(Controller)而已。可是,为什么在一个开源社区里,会同时存在这样的两个核心原理完全一致、设计目标也几乎相同的有状态应用管理方案呢?作为 CoreOS 公司后来广为人知的“左膀右臂”之一(即:etcd 和 Operator),Operator 项目能够在 Kubernetes 生态里争取到今天的位置,是不是也是 CoreOS 公司的开源战略使然呢?事实上,Operator 项目并没有像很多人想象的那样出生就含着金钥匙。只不过,在当时的确没有人能想到,当 CoreOS 的两名工程师带着一个业余项目从一间平淡无奇的公寓走出后不久,一场围绕着 Kubernetes API 生态、以争夺“分布式应用开发者”为核心的的重量级角逐,就徐徐拉开了序幕。*2016 年秋天,原 CoreOS 公司的工程师邓洪超像往常一样,来到了同事位于福斯特城(Foster City)的公寓进行结对编程。每周四相约在这里结对,是这两位工程师多年来约定俗成的惯例。不过,与以往不同的是,相比于往常天马行空般的头脑风暴,这一次,这两位工程师的脑子里正在琢磨着的,是一个非常“接地气”的小项目。我们知道,Kubernetes 项目实现“容器编排”的核心,在于一个叫做“控制器模式”的机制,即:通过对 etcd 里的 API 对象的变化进行监视(Watch),Kubernetes 项目就可以在一个叫做 Controller 的组件里对这些变化进行响应。而无论是 Pod 等应用对象,还是 iptables、存储设备等服务对象,任何一个 API 对象发生变化,那么 Kubernetes 接下来需要执行的响应逻辑,就是对应的 Controller 里定义的编排动作。所以,一个自然而然的想法就是,作为 Kubernetes 项目的用户,我能不能自己编写一个 Controller 来定义我所期望的编排动作呢?比如:当一个 Pod 对象被更新的时候,我的 Controller 可以在“原地”对 Pod 进行“重启”,而不是像 Deployment 那样必须先删除 Pod,然后再创建 Pod。这个想法,其实是很多应用开发者以及 PaaS 用户的强烈需求,也是一直以来萦绕在 CoreOS 公司 CEO Alex Polvi 脑海里的一个念头。而在一次简单的内部讨论提及之后,这个念头很快就激发出了两位工程师的技术灵感,成为了周四结对编程的新主题。而这一次,他们决定把这个小项目,起名叫做:Operator。所以顾名思义,Operator 这个项目最开始的初衷,是用来帮助开发者实现运维(Operate)能力的。但 Operator 的核心思想,却并不是“替开发者做运维工作”,而是“让开发者自己编写运维工具”。更有意思的是,这个运维工具的编写标准,或者说,编写 Operator 代码可以参考的模板,正是 Kubernetes 的“控制器模式(Controller Pattern)”。前面已经说过, Kubernetes 的“控制器模式”,是围绕着比如 Pod 这样的 API 对象,在 Controller 通过响应它的增删改查来定义对 Pod 的编排动作。而 Operator 的设计思路,就是允许开发者在 Kubernetes 里添加一个新的 API 对象,用来描述一个分布式应用的集群。然后,在这个 API 对象的 Controller 里,开发者就可以定义对这个分布式应用集群的运维动作了。举个例子, 假设下面这个 YAML 文件定义的,是一个 3 节点 etcd 集群的描述:有了这样一个 etcdCluster 对象,那么开发者接下来要做的事情,就是编写一个 etcdCluster Controller,使得当任何用户提交这样一个 YAML 文件给 Kubernetes 之后,我们自己编写的 Controller 就会响应 etcdCluster “增加”事件,为用户创建出 3 个节点的 etcd 集群出来。然后,它还会按照我们在 Controller 编写的事件响应逻辑,自动的对这个集群的节点更新、删除等事件做出处理,执行我定义的其他运维功能。像这样一个 etcdCluster Controller,就是 etcd Operator 的核心组成部分了。而作为 etcd 的开发者,CoreOS 的两位工程师把对 etcd 集群的运维工作编写成 Go 语言代码,一点都不困难。可是,要完成这个 Operator 真正困难在于:Kubernetes 只认识 Pod、Node、Service 等这些 Kubernetes 自己原生的 API 对象,它怎么可能认识开发者自己定义的这个 etcdCluster 对象呢?在当时, Kubernetes 项目允许用户自己添加 API 对象的插件能力,叫做 Third Party Resource,简称:TPR。TPR 允许你提交一个 YAML 文件,来定义你想要的的新 API 对象的名字,比如:etcdCluster;也允许你定义这个对象允许的合法的属性,比如:int 格式的 size 字段, string 格式的 version 字段。然后,你就可以提交一个具体的 etcdCluster 对象的描述文件给 Kubernetes,等待该对应的 Controller 进行处理。而这个 Controller,就是 Operator 的主干代码了。所以接下来,CoreOS 的两位工程师轻车熟路,在 Operator 里对 etcdCluster 对象的增、删、改事件的响应位置,写上了创建、删除、更新 etcd 节点的操作逻辑。然后,调试运行,看着一个 etcd 集群按照 YAML 文件里的描述被创建起来。大功告成!就这样,在一个普通的周四下午,世界上第一个 Operator 诞生在了湾区的一所公寓当中。而对于 CoreOS 的两位工程师来说,编写这个小工具的主要目的,就是借助 Kubernetes 的核心原理来自动化的管理 etcd 集群,更重要的是,不需要使用 Kubernetes 里自带的 StatefulSet。你可能已经知道,Kubernetes 里本身就内置了一个叫做 StatefulSet 的功能,是专门用来管理有状态应用的。而 StatefulSet 的核心原理,其实是对分布式应用的两种状态进行了保持:分布式应用的拓扑状态,或者说,节点之间的启动顺序;分布式应用的存储状态,或者说,每个节点依赖的持久化数据。可是,为了能够实现上述两种状态的保持机制,StatefulSet 的设计就给应用开发者带来了额外的束缚。比如,etcd 集群各节点之间的拓扑关系,并不依赖于节点名字或者角色(比如 Master 或者 Slave)来确定,而是记录在每个 etcd 节点的启动参数当中。这使得 StatefulSet 通过“为节点分配有序的 DNS 名字”的拓扑保持方式,实际上没有了用武之地,反而还得要求开发者在节点的启动命令里添加大量的逻辑来生成正确的启动命令,非常不优雅。类似的,对于存储状态来说,etcd 集群对数据的备份和恢复方法,也跟 StatefulSet 依赖的的远程持久化数据卷方案并没有太大关系。不难看到, StatefulSet 其实比较适用于应用本身节点管理能力不完善的项目,比如 MySQL。而对于 etcd 这种已经借助 Raft 实现了自管理的分布式应用来说, StatefulSet 的使用方法和带来的各种限制,其实是非常别扭的。而带着工程师特有的较真儿精神,邓洪超和他的同事借助 Kubernetes 原生的扩展机制实现的,正是一个比 StatefulSet 更加灵活、能够把控制权重新交还给开发者的分布式应用管理工具。他们把这个工具起名叫做 Operator,并在几个月后的 KubeCon 上进行了一次 Demo ,推荐大家尝试使用 Operator 来部署 etcd 集群。没有人能想到的是,这个当时还处于 PoC 状态的小项目一经公布,就立刻激发起了整个社区的模仿和学习的热潮。很快,大量的应用开发者纷纷涌进 Kubernetes 社区,争先恐后的宣布自己的分布式项目可以通过 Operator 运行起来。而敏锐的公有云提供商们很快看出了这其中的端倪:Operator 这个小框架,已然成为了分布式应用和有状态应用“上云”的必经之路。Prometheus,Rook,伴随着越来越多的、以往在容器里运行起来困难重重的应用,通过 Operator 走上了 Kubernetes 之后,Kubernetes 项目第一次出现在了开发者生态的核心位置。这个局面,已经远远超出了邓洪超甚至 CoreOS 公司自己的预期。更重要的是,不同于 StatefulSet 等 Kubernetes 原生的编排概念,Operator 依赖的 Kubernetes 能力,只有最核心的声明式 API 与控制器模式;Operator 具体的实现逻辑,则编写在自定义 Controller 的代码中。这种设计给开发者赋予了极高的自由度,这在整个云计算和 PaaS 领域的发展过程中,都是非常罕见的。此外,相比于 Helm、Docker Compose 等描述应用静态关系的编排工具,Operator 定义的乃是应用运行起来后整个集群的动态逻辑。得益于 Kubernetes 项目良好的声明式 API 的设计和开发者友好的 API 编程范式,Operator 在保证上述自由度的同时,又可以始终如一的展现出清晰的架构和设计逻辑,使得应用的开发者们,可以通过复制粘贴就快速搭建出一个 Operator 的框架,然后专注于填写自己的业务逻辑。在向来讲究“用脚投票”的开发者生态当中,Operator 这样一个编程友好、架构清晰、方便代码复制粘贴的小工具,本身就已经具备了某些成功的特质。然而,Operator 的意外走红,并没有让 CoreOS 公司“一夜成名”,反而差点将这个初出茅庐的项目,扼杀在萌芽状态。在当时的 Kubernetes 社区里,跟应用开发者打交道并不是一个非常常见的事情。而 Operator 项目的诞生,却把 Kubernetes 项目第一次拉近到了开发者的面前,这让整个社区感觉了不适应。而作为 Kubernetes 项目 API 治理的负责人,Google 团队对这种冲突的感受最为明显。对于 Google 团队来说,Controller 以及控制器模式,应该是一个隐藏在 Kubernetes 内部实现里的核心机制,并不适合直接开放给开发者来使用。退一步说,即使开放出去,这个 Controller 的设计和用法,也应该按照 Kubernetes 现有的 API 层规范来进行,最好能成为 Kubernetes 内置 Controller Manager 管理下的一部分。可是, Operator 却把直接编写 Controller 代码的自由度完全交给了开发者,成为了一个游离于 Kubernetes Controller Manager 之外的外部组件。带着这个想法,社区里的很多团队从 Operator 项目诞生一开始,就对它的设计和演进方向提出了质疑,甚至建议将 Operator 的名字修改为 Custom Kubernetes Controller。而无巧不成书,就在 Google 和 CoreOS 在 Controller 的话语权上争执不下的时候, Kubernetes 项目的发起人之一 Brendan Burns 突然宣布加入了微软,这让 Google 团队和 Operator 项目的关系一下子跌倒了冰点。你可能会有些困惑:Brendan Burns 与 Kubernetes 的关系我是清楚的,但这跟 Operator 又有什么瓜葛吗?实际上,你可能很难想到,Brendan Burns 和他的团队,才是 TPR (Third Party Resource)这个特性最初的发起人。所以,几乎在一夜之间,Operator 项目链路上的每一个环节,都与 Google 团队完美的擦肩而过。眼睁睁的看着这个正冉冉升起的开发者工具突然就跟自己完全没了关系,这个中滋味,确实不太好受。于是,在 2017年初,Google 团队和 RedHat 公司开始主动在社区推广 UAS(User Aggregated APIServer),也就是后来 APIServer Aggregator 的雏形。APIServer Aggregator 的设计思路是允许用户编写一个自定义的 APIServer,在这里面添加自定义 API。然后,这个 APIServer 就可以跟 Kubernetes 原生的 APIServer 绑定部署在一起统一提供服务了。不难看到,这个设计与 Google 团队认为自定义 API 必须在 Kubernetes 现有框架下进行管理的想法还是比较一致的。紧接着,RedHat 和 Google 联盟开始游说社区使用 UAS 机制取代 TPR,并且建议直接从 Kubernetes 项目里废弃 TPR 这个功能。一时间,社区里谣言四起,不少已经通过 TPR 实现的项目,也开始转而使用 UAS 来重构以求自保。 而 Operator 这个严重依赖于 TPR 的小项目,还没来得及发展壮大,就被推向了关闭的边缘。面对几乎要与社区背道而驰的困境,CoreOS 公司的 CTO Brandon Philips 做出了一个大胆的决定:让社区里的所有开发者发声,挽救 TPR 和 Operator。2017 年 2月,Brandon Philips 在 GitHub 上开了一个帖子(Gist), 号召所有使用 TPR 或者 Operator 项目的开发者在这里留下的自己的项目链接或者描述。这个帖子,迅速的成为了当年容器技术圈最热门的事件之一,登上了 HackerNews 的头条。有趣的是,这个帖子直到今天也仍然健在,甚至还在被更新,你可以点击这个链接去感受一下当时的盛况。https://gist.github.com/philips/a97a143546c87b86b870a82a753db14c而伴随着 Kubernetes 项目的迅速崛起,短短一年时间不到,夹缝中求生存的 Operator 项目,开始对公有云市场产生了不可逆转的影响,也逐步改变了开发者们对“云”以及云上应用开发模式的基本认知。甚至就连 Google Cloud 自己最大的客户之一 Snapchat ,也成为了 Operator 项目的忠实用户。在来自社区的巨大压力下,在这个由成千上万开发者们自发维护起来的 Operator 生态面前,Google 和 RedHat 公司最终选择了反省和退让。有意思的是,这个退让的结果,再一次为这次闹剧增添了几分戏剧性。就在 Brandon Phillips 的开发者搜集帖发布了不到三个月后,RedHat 和 Google 公司的工程师突然在 Kubernetes 社区里宣布:TPR 即将被废弃,取而代之的是一个名叫 CRD,Custom Resource Definition 的东西。于是,开发者们开始忧心忡忡的按照文档,将原本使用 TPR 的代码都升级成 CRD。而就在这时,他们却惊奇的发现,这两种机制除了名字之外,好像并没有任何不同。所谓的升级工作,其实就是将代码里的 TPR 字样全局替换成 CRD 而已。难道,这只是虚惊一场?其实,很少有人注意到,在 TPR 被替换成 CRD 之后,Brendan Burns 和微软团队就再也没有出现在“自定义 API”这个至关重要的领域里了。而 CRD 现在的负责人,都是来自 Google 和 RedHat 的工程师。在这次升级事件之后不久,CoreOS 公司在它的官方网站上发布了一篇叫做:TPR Is Dead! Kubernetes 1.7 Turns to CRD 的博客(https://coreos.com/blog/custom-resource-kubernetes-v17),旨在指导用户从 TRP 升级成 CRD。不过,现在回头再看一眼这篇文章,平淡无奇的讲述背后,你能否感受到当年这场“开发者战争”的蛛丝马迹呢?其实,Operator 并不平坦的晋级之路,只是 Kubernetes API 生态风起云涌的冰山一角。几乎在每个星期,甚至每一天,都有太多围绕着 Kubernetes 开发者生态的角逐,在这个无比繁荣的社区背后,以不为人知的方式开始或者谢幕。而这一切纷争的根本原因却无比直白。Kubernetes 项目,已经被广泛认可为云计算时代应用开发者们的终端入口。这正是为何,无论是 Google、微软,还是 CoreOS 以及 Heptio,所有这个生态里的大小玩家,都在不遗余力的在 Kubernetes API 层上捍卫着自己的话语权,以期在这个未来云时代的开发者入口上,争取到自己的一席之地。而在完成了对收 CoreOS 的收购之后,RedHat 终于在这一领域拿到了可以跟 Google 和微软一较高低的关键位置。2018年,RedHat 不失时机的发布了 Operator Framework,希望通过 Operator 周边工具和生态的进一步完善,把 Operator 确立成为分布式应用开发与管理的关键依赖。而伴随着 Operator 越来越多的介入到应用开发和部署流程之后, Kubernetes API 一定会继续向上演进,进一步影响开发者的认知和编程习惯。这,已经成为了云计算生态继续发展下去的必然趋势。而作为这个趋势坚定不移的贯彻者,无论是 Istio,还是 Knative,都在用同样的经历告诉我们这样的道理:只有构建在 Kubernetes 这个云时代基础设施事实标准上的开发者工具,才有可能成为下一个开发者领域的 “Operator” 。本文作者:amber涂南阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

January 11, 2019 · 4 min · jiezi

容器监控实践—cAdvisor

概述为了解决docker stats的问题(存储、展示),谷歌开源的cadvisor诞生了,cadvisor不仅可以搜集一台机器上所有运行的容器信息,还提供基础查询界面和http接口,方便其他组件如Prometheus进行数据抓取,或者cadvisor + influxdb + grafna搭配使用。cAdvisor可以对节点机器上的资源及容器进行实时监控和性能数据采集,包括CPU使用情况、内存使用情况、网络吞吐量及文件系统使用情况Cadvisor使用Go语言开发,利用Linux的cgroups获取容器的资源使用信息,在K8S中集成在Kubelet里作为默认启动项,官方标配。安装1.使用二进制部署下载二进制:https://github.com/google/cadvisor/releases/latest本地运行:./cadvisor -port=8080 &>>/var/log/cadvisor.log2.使用docker部署docker run \ –volume=/:/rootfs:ro \ –volume=/var/run:/var/run:rw \ –volume=/sys:/sys:ro \ –volume=/var/lib/docker/:/var/lib/docker:ro \ –volume=/dev/disk/:/dev/disk:ro \ –publish=8080:8080 \ –detach=true \ –name=cadvisor \ google/cadvisor:latest注意:在Ret Hat,CentOS, Fedora 等发行版上需要传递如下参数,因为 SELinux 加强了安全策略:–privileged=true 启动后访问:http://127.0.0.1:8080查看页面,/metric查看指标* 常见指标:http://yjph83.iteye.com/blog/2394091* 指标分析:https://luoji.live/cadvisor/cadvisor-source-code-metrics-20160927.html`3.kubernetes中使用* Daemonset部署: https://github.com/google/cadvisor/tree/master/deploy/kubernetes* kubelet自带cadvisor监控所有节点,可以设置–cadvisor-port=8080指定端口(默认为4194)* kubernetes 在2015-03-10 这个提交(Run cAdvisor inside the Kubelet. Victor Marmol 2015/3/10 13:39)中cAdvisor开始集成在kubelet中,目前的1.6及以后均存在注意:从 v1.7 开始,Kubelet metrics API 不再包含 cadvisor metrics,而是提供了一个独立的 API 接口:* Kubelet metrics: http://127.0.0.1:8001/api/v1/proxy/nodes/<node-name>/metrics* Cadvisor metrics: http://127.0.0.1:8001/api/v1/proxy/nodes/<node-name>/metrics/cadvisorcadvisor 监听的端口将在 v1.12 中删除,建议所有外部工具使用 Kubelet Metrics API 替代。常用搭配1.cAdvisor+Heapster+influxdbHeapster:在k8s集群中获取metrics和事件数据,写入InfluxDB,heapster收集的数据比cadvisor多,却全,而且存储在influxdb的也少。Heapster将每个Node上的cAdvisor的数据进行汇总,然后导到InfluxDB。Heapster的前提是使用cAdvisor采集每个node上主机和容器资源的使用情况,再将所有node上的数据进行聚合。这样不仅可以看到Kubernetes集群的资源情况,还可以分别查看每个node/namespace及每个node/namespace下pod的资源情况。可以从cluster,node,pod的各个层面提供详细的资源使用情况。InfluxDB:时序数据库,提供数据的存储,存储在指定的目录下。Grafana:提供了WEB控制台,自定义查询指标,从InfluxDB查询数据并展示。cAdvisor+Prometheus+Grafana访问http://localhost:8080/metrics,可以拿到cAdvisor暴露给 Prometheus的数据其他内容参考后续的prometheus文章深入解析cAdvisor结构图cadvisor地址:https://github.com/google/cad…主函数逻辑:(cadvisor/cadvisor.go)通过new出来的memoryStorage以及sysfs实例,创建一个manager实例,manager的interface中定义了许多用于获取容器和machine信息的函数核心函数:生成manager实例的时候,还需要传递两个额外的参数,分别是maxHousekeepingInterval:存在内存的时间,默认60sallowDynamicHousekeeping:是否允许动态配置housekeeping,也就是下一次开始搜集容器信息的时间,默认true因为需要暴露服务,所以在handler文件中,将上面生成的containerManager注册进去(cadvisor/http/handler.go),之后就是启动manager,运行其Start方法,开始搜集信息,存储信息的循环操作。以memory采集为例:具体的信息还是通过runc/libcontainer获得,libcontainer是对cgroup的封装。在/sys/fs/cgroup/memory中包含大量的了memory相关的信息(参考docker原生监控文章)Prometheus的收集器(cadvisor/metrics/prometheus.go)更多源码参考文章:https://luoji.live/categories…总结优缺点:优点:谷歌开源产品,监控指标齐全,部署方便,而且有官方的docker镜像。缺点:是集成度不高,默认只在本地保存1分钟数据,但可以集成InfluxDB等存储备注:爱奇艺参照cadvisor开发的dadvisor,数据写入graphite,等同于cadvisor+influxdb,但dadvisor并没有开源container-monitor-book系列 : https://yasongxu.gitbook.io/container-monitor/ ...

January 8, 2019 · 1 min · jiezi

容器监控实践—Docker原生

前言传统虚机监控一般采用类似Zabbix的方案,但容器出现之后,再使用Zabbix agent来采集数据的话就显得有些吃力了,如果每个容器都像OS那样监控,则metric数量将会非常巨大,而且这些数据很可能几分钟之后就没有意义了(容器已经停止或漂移),且容器的指标汇总更应该是按照APP甚至POD维度。如果只是过渡方案,或者想将容器监控统一到公司现有的Zabbix中,可以参考zabbix-docker-monitoring,有很多模板如:zabbix-template-app-docker.xml参考文章:https://segmentfault.com/a/11…Docker原生监控常用方式:docker ps/top/logsdocker statsdocker Remote APIdocker 伪文件系统docker stats该命令默认以流式方式输出,如果想打印出最新的数据并立即退出,可以使用 no-stream=true 参数。可以指定一个已停止的容器,但是停止的容器不返回任何数据。例如:Remote APIDocker Remote API是一个取代远程命令行界面(rcli)的REST API如:curl http://127.0.0.1:4243/containers/json可以使用API来获取监控数据并集成到其他系统,注意不要给Docker daemon带来性能负担,如果你一台主机有很多容器,非常频繁的采集可能会大量占据CPU伪文件系统以下操作的环境为:Centos7系统 docker17.03版本docker stats的数据来自于/sys/fs/cgroup下的文件mem usage那一列的值,来自于/sys/fs/cgroup/memory/docker/[containerId]/memory.usage_in_bytes如果没限制内存,Limit = machine_mem,否则来自于/sys/fs/cgroup/memory/docker/[id]/memory.limit_in_bytes内存使用率 = memory.usage_in_bytes/memory.limit_in_bytes一般情况下,cgroup文件夹下的内容包括CPU、内存、磁盘、网络等信息:如memory下的文件有:几个常用的指标含义:memory.stat中的信息是最全的:更多资料参考:cgroup memory原理分析:Libcontainer 深度解析总结优缺点:优点:原生,很方便的看到当前宿主机上所有容器的CPU、内存、网络流量等数据。缺点:只能统计当前宿主机的所有容器,数据是实时的,没有存储,没有报警,没有可视化。备注:1.如果你没有限制容器内存,那么docker stats将显示您的主机的内存总量。但它并不意味着你的每个容器都能访问那么多的内存2.默认时stats命令会每隔1秒钟刷新一次,如果只看当前状态:docker stats –no-stream3.指定查看某个容器的资源可以指定名称或PID: docker stats –no-stream registry 1493container-monitor-book系列 : https://yasongxu.gitbook.io/container-monitor/

January 8, 2019 · 1 min · jiezi

基于Heapster的HPA

概述Horizontal Pod Autoscaling,简称HPA,是Kubernetes中实现POD水平自动伸缩的功能。自动扩展主要分为两种:水平扩展(scale out),针对于实例数目的增减垂直扩展(scal up),即单个实例可以使用的资源的增减, 比如增加cpu和增大内存HPA属于前者。它可以根据CPU使用率或应用自定义metrics自动扩展Pod数量(支持 replication controller、deployment 和 replica set)监控数据获取Heapster: heapster收集Node节点上的cAdvisor数据,并按照kubernetes的资源类型来集合资源。但是在v1.11中已经被废弃(heapster监控数据可用,但HPA不再从heapster拿数据)metric-server: 在v1.8版本中引入,官方将其作为heapster的替代者。metric-server依赖于kube-aggregator,因此需要在apiserver中开启相关参数。v1.11中HPA从metric-server获取监控数据工作流程1.创建HPA资源,设定目标CPU使用率限额,以及最大、最小实例数, 一定要设置Pod的资源限制参数: request, 负责HPA不会工作。2.控制管理器每隔30s(可以通过–horizontal-pod-autoscaler-sync-period修改)查询metrics的资源使用情况3.然后与创建时设定的值和指标做对比(平均值之和/限额),求出目标调整的实例个数4.目标调整的实例数不能超过1中设定的最大、最小实例数,如果没有超过,则扩容;超过,则扩容至最大的实例个数如何部署:1.6-1.10版本默认使用heapster1.11版本及以上默认使用metric-server1.部署和运行php-apache并将其暴露成为服务2.创建HPA如果为1.8及以下的k8s集群,指标正常,如果为1.11集群,需要执行如下操作。4.向php-apache服务增加负载,验证自动扩缩容 启动一个容器,并通过一个循环向php-apache服务器发送无限的查询请求(请在另一个终端中运行以下命令)5.观察HPA是否生效container-monitor-book系列 : https://yasongxu.gitbook.io/container-monitor/

January 8, 2019 · 1 min · jiezi

容器监控实践—Heapster

概述该项目将被废弃(RETIRED)Heapster是Kubernetes旗下的一个项目,Heapster是一个收集者,并不是采集1.Heapster可以收集Node节点上的cAdvisor数据:CPU、内存、网络和磁盘2.将每个Node上的cAdvisor的数据进行汇总3.按照kubernetes的资源类型来集合资源,比如Pod、Namespace4.默认的metric数据聚合时间间隔是1分钟。还可以把数据导入到第三方工具ElasticSearch、InfluxDB、Kafka、Graphite5.展示:Grafana或Google Cloud Monitoring使用场景Heapster+InfluxDB+Grafana共同组成了一个流行的监控解决方案Kubernetes原生dashboard的监控图表信息来自heapster在HPA(Horizontal Pod Autoscaling)中也用到了Heapster,HPA将Heapster作为Resource Metrics API,向其获取metric,作为水平扩缩容的监控依据监控指标流程:1.Heapster首先从apiserver获取集群中所有Node的信息。2.通过这些Node上的kubelet获取有用数据,而kubelet本身的数据则是从cAdvisor得到。3.所有获取到的数据都被推到Heapster配置的后端存储中,并还支持数据的可视化。部署docker部署:k8s中部署:heapster.ymlinfluxdb.yml注意修改镜像地址,k8s.gcr.io无法访问的话,修改为内网镜像地址,如替换为registry.cn-hangzhou.aliyuncs.com/google_containersHeapster的参数source: 指定数据获取源,如kube-apiserverinClusterConfig:kubeletPort: 指定kubelet的使用端口,默认10255kubeletHttps: 是否使用https去连接kubelets(默认:false)apiVersion: 指定K8S的apiversioninsecure: 是否使用安全证书(默认:false)auth: 安全认证useServiceAccount: 是否使用K8S的安全令牌sink: 指定后端数据存储,这里指定influxdb数据库Metrics列表深入解析架构图:代码结构(https://github.com/kubernetes…)heapster主函数(heapster/metrics/heapster.go)主要流程:创建数据源对象创建后端存储对象list创建处理metrics数据的processors创建manager,并开启数据的获取及export的协程开启Heapster server,并支持各类APIcAdvisor返回的原始数据包含了nodes和containers的相关数据,heapster需要创建各种processor,用于处理成不同类型的数据,比如pod, namespace, cluster,node的聚合,求和平均之类,processor有如下几种:例如Pod的处理如下:详细解析参考: https://segmentfault.com/a/11…现状heapster已经被官方废弃(k8s 1.11版本中,HPA已经不再从hepaster获取数据)CPU内存、HPA指标: 改为metrics-server基础监控:集成到prometheus中,kubelet将metric信息暴露成prometheus需要的格式,使用Prometheus Operator事件监控:集成到https://github.com/heptiolabs…基于Heapster的HPA参考:基于Heapster的HPAcontainer-monitor-book系列 : https://yasongxu.gitbook.io/container-monitor/

January 8, 2019 · 1 min · jiezi

容器监控实践—开篇

概述随着越来越多的线上服务docker化,对容器的监控、报警变得越来越重要,容器监控有多种形态,有些是开源的(如promethues),而另一些则是商业性质的(如Weave),有些是集成在云厂商一键部署的(Rancher、谷歌云),有些是手动配置的,可谓百花齐放。本文将对现有的容器监控方案进行总结对比,监控解决方案的数量之多令人望而生畏,新的解决方案又不断涌现,下面将从开源方案(采集、展示、告警)、商业方案、云厂商、主机监控、日志监控、服务监控等方面进行列举,此篇为概述篇,包含汇总列表及脑图,具体分析将在后面补充更新,欢迎指正、补充。方案汇总一. 开源方案1.采集:Docker StatscAdvisorHeapstermetrics-serverCustom Metricskube-state-metricsnode-exporterPrometheusDockbix agentcortex2.展示:GrafanaKibanaVizceralmozaikZabbix dashboard3.报警:AlertManagerconsul-alertselastalertBosunCabot二. 商业方案SysdigDataDogdynatraceWeaveThanosCosalefreshtracksnewrelicSensunetsilpingdom三. 云厂商Google cloudAWS腾讯云阿里云百度云华为云四. 主机监控Zabbixnagiosnetdata五. 日志监控ELK StackEFK StackelastalertGraylogdocker_monitoring_logging_alerting六. 服务监控JaegerZipkinkubewatchriemann七. 存储后端InfluxDBKafkaGraphiteOpenTSDBElasticSearch脑图container-monitor-book系列 : https://yasongxu.gitbook.io/container-monitor/

January 8, 2019 · 1 min · jiezi

一文带你看透kubernetes 容器编排系统

本文由云+社区发表作者:turboxuKubernetes作为容器编排生态圈中重要一员,是Google大规模容器管理系统borg的开源版本实现,吸收借鉴了google过去十年间在生产环境上所学到的经验与教训。 Kubernetes提供应用部署、维护、 扩展机制等功能,利用Kubernetes能方便地管理跨机器运行容器化的应用。当前Kubernetes支持GCE、vShpere、CoreOS、OpenShift、Azure等平台,除此之外,也可以直接运行在物理机上.kubernetes是一个开放的容器调度管理平台,不限定任何一种言语,支持java/C++/go/python等各类应用程序 。kubernetes是一个完备的分布式系统支持平台,支持多层安全防护、准入机制、多租户应用支撑、透明的服务注册、服务发现、内建负载均衡、强大的故障发现和自我修复机制、服务滚动升级和在线扩容、可扩展的资源自动调度机制、多粒度的资源配额管理能力,完善的管理工具,包括开发、测试、部署、运维监控,一站式的完备的分布式系统开发和支撑平台。一. 系统架构kubernetes系统按节点功能由master和node组成。MasterMaster作为控制节点,调度管理整个系统,包含以下组件:API Server作为kubernetes系统的入口,封装了核心对象的增删改查操作,以RESTful接口方式提供给外部客户和内部组件调用。它维护的REST对象将持久化到etcd。Scheduler:负责集群的资源调度,为新建的pod分配机器。这部分工作分出来变成一个组件,意味着可以很方便地替换成其他的调度器。Controller Manager:负责执行各种控制器,目前有两类:Endpoint Controller:定期关联service和pod(关联信息由endpoint对象维护),保证service到pod的映射总是最新的。Replication Controller:定期关联replicationController和pod,保证replicationController定义的复制数量与实际运行pod的数量总是一致的。NodeNode是运行节点,运行业务容器,包含以下组件:Kubelet:责管控docker容器,如启动/停止、监控运行状态等。它会定期从etcd获取分配到本机的pod,并根据pod信息启动或停止相应的容器。同时,它也会接收apiserver的HTTP请求,汇报pod的运行状态。Kube Proxy:负责为pod提供代理。它会定期从etcd获取所有的service,并根据service信息创建代理。当某个客户pod要访问其他pod时,访问请求会经过本机proxy做转发。借用一张网图,表达功能组件之间关系:二.基本概念Nodenode是kubernetes集群中相对于master而言的工作主机,在较早版本中也被称为minion。Node可以是一台物理主机,也可以是一台虚拟机(VM)。在每个node上运行用于启动和管理pod的服务——kubelet,并能够被master管理。在node上运行的服务进程包括kubelet、kube-proxy和docker daemon。Node的信息如下:node地址:主机的IP地址或者nodeid。node的运行状态: pending,running,terminated。node condition: 描述running状态node的运行条件,目前只有一种条件Ready,表示node处于健康状态,可以接收master发来的创建pod的指令。node系统容量:描述node可用的系统资源,包括CPU、内存、最大可调度pod数量等。Podpod 是 kubernetes 的最基本操作单元,包括一个或多个紧密相关的容器,一个 pod 可以被一个容器化的环境看作应用层的“逻辑宿主机”( Logical host )。一个 pod 中的多个容器应用通常是紧耦合的。Pod 在 node 上被创建、启动或者销毁。为什么 kubernetes 使用 pod 在容器之上再封装一层呢?一个很重要的原因是,docker 容器之间通信受到 docker 网络机制的限制。在 docker 的,世界中,一个容器需要通过 link 方式才能访问另一个容器提供的服务(端口)。大量容器之间的 link 将是一个非常繁重的工作。通过 pod 的概念将多个容器组合在一个虚拟的“主机”内,可以实现容器之间仅需通过 localhost 就能相互通信了。一个pod中的应用容器共享一组资源,如:pid命名空间:pod中的不同应用程序可以看到其他的进程PID网络命名空间:pod中的多个容器能够访问同一个IP和端口范围IPC命名空间:pod中的多个容器能够使用systemV ipc 或POSIX消息队列进行通信。UTS命名空间:pod中的多个容器共享一个主机名。Volumes(共享存储卷):pod中的各个容器可以访问在pod级别定义的volumes。Labellabel是kubernetes系统中的一个核心概念。Label以key/value键值对的形式附加到各种对象上,如pod、service、RC、Node等。Label定义了这些对象的可识别属性,用来对它们进行管理和选择。Label可以在创建对象时附加到对象上,也可以在对象创建后通过API进行管理。在为对象定义好label后,其他对象就可以使用label selector来定义其他作用的对象了。label selector的定义由多个逗号分隔的条件组成: “label”: { “key1”: ”value1”, “key2”: ”value2” }Resource controller(RC)Resource controller(RC)是kubernetes系统中的核心概念,用于定义pod副本的数量。在master的Controller manager进程通过RC的定义来完成pod的创建、监控、启停等操作。根据replication controller的定义,kubernetes能够确保在任意时刻都能运行用户指定的pod“副本”(replica)数量。如果有过多的的pod副本在运行,系统会停掉一些pod;如果运行的pod副本数量太少,系统就会再启动一些pod,总之,通过RC的定义,kubernetes总是保证集群中运行着用户期望副本数量。Service(服务)在kubernetes的世界里,虽然每个pod都会被分配一个单独的IP地址,但这个IP地址会随着pod的销毁而消失。这就引出一个问题:如果有一组pod组成一个集群来提供服务,那么如何来访问它们呢?kubernetes的service就是用来解决这个问题的核心概念。一个service可以看作一组提供相同服务的pod的对外访问接口。Service作用于哪些pod是通过label selector 来定义的。pod的IP地址是docker daemon根据docker0网桥的IP地址段进行分配的,但service的Cluster IP地址是kubernetes系统中的虚拟IP地址,由系统动态分配。 Service的ClusterIP地址相对于pod的IP地址来说相对稳定,service被创建时即被分配IP地址,在销毁该service之前,这个IP地址都不会再变化。由于service对象在Cluster IP Range池中分配到的IP只能在内部访问,所以其他pod都可以无障碍地访问到它。但如果这个service作为前端服务,准备为集群外的客户端提供服务,我们就需要给这个服务提供公共IP了。kubernetes支持两种对外提供服务的service的type定义:nodeport和loadbalancer。Volume(存储卷)volume是pod中能够被多个容器访问的共享目录。Kubernetes的volume概念与docker的volume比较类似,但并不完全相同。Kubernetes中的volume与pod生命周期相同,但与容器的生命周期不相关。当容器终止或重启时,volume中的数据也不会丢失。另外,kubernetes支持多种类型的volume,并且一个pod可以同时使用任意多个volume。(1)EmptyDir:一个EmptyDir volume是在pod分配到Node时创建的。从它的名称就可以看出,它的初始内容为空。在同一个pod中所有容器可以读和写EmptyDir中的相同文件。当pod从node上移除时,EmptyDir中的数据也会永久删除。(2)hostPath:在pod上挂载宿主机上的文件或目录。通常用于: 容器应用程序生成的日志文件需要永久保存,可以使用宿主机的高速文件系统进行存储; 需要访问宿主机上的docker引擎内部数据结构的容器应用,可以通过定义hostPath为宿主机/var/lib/docker目录,使容器内部应用可以直接访问docker的文件系统。(3)gcePersistentDick:使用这种类型的volume表示使用谷歌计算引擎(Google Compute Engine, GCE)上永久磁盘(persistent disk,PD)上的文件。与EmptyDir不同,PD上的内容会永久保存,当pod被删除时,PD只是被卸载(unmount),但不会被删除。需要注意的是,你需要先创建一个永久磁盘(PD)才能使用gcePersistentDisk。(4)awsElasticBlockStore:与GCE类似,该类型的volume使用Amazon提供的Amazon Web Service(AWS)的EBS Volume,并可以挂载到pod中去。需要注意的是,需要先创建一个EBS Volume才能使用awsElasticBlockStore。(5)nfs:使用NFS(网络文件系统)提供的共享目录挂载到Pod中。在系统中需要一个支行中的NFS系统。(6)iscsi:使用iSCSI存储设备上的目录挂载到pod中。(7)glusterfs:使用开源BlusterFS网络文件系统的目录挂载到pod中。(8)rbd:使用Linux块设备共享存储(Rados Block Device)挂载到pod中。(9)gitRepo:通过挂载一个空目录,并从GIT库clone一个git repository以供pod使用。(10)secret:一个secret volume用于为pod提供加密的信息,你可以将定义在kubernetes中的secret直接挂载为文件让pod访问。Secret volume是通过tmfs(内存文件系统)实现的,所以这种类型的volume总是不会持久化的。(11)persistentVolumeClaim:从PV(persistentVolume)中申请所需的空间,PV通常是种网络存储,如GCEPersistentDisk、AWSElasticBlockStore、NFS、iSCSI等。Namespace(命名空间)namespace(命名空间)是kubernetes系统中另一个非常重要的概念,通过将系统内部的对象“分配”到不同的namespace中,形成逻辑上分组的不同项目、小组或用户组,便于不同的分组在共享使用整个集群的资源的同时还能分别管理。kubernetes集群在启动后,会创建一个名为“default”的namespace。接下来,如果不特别指明namespace,则用户创建的pod、RC、Service都将被系统创建到名为“default”的namespace中。使用namespace来组织kubernetes的各种对象,可以实现对用户的分组,即“多租户”管理。对不同的租房还可以进行单独的资源配额设备和管理,使得整个集群配置非常灵活、方便。Annotation(注解)annotation与label类似,也使用key/value键值对的形式进行定义。Label具有严格的全名规则,它定义的是kubernetes对象的元数据(metadata),并且用于label selector。Annotation则是用户任意定义的“附加”信息,以便于外部工具进行查找。用annotation来记录的信息包括: build信息、release信息、docker镜像信息等,如时间戳、release id号、PR号、镜像hash值、docker Controller地址等。典型流程以创建一个Pod为例,kubernetes典型的流程如下图所示:三.组件Replication Controller为了区分Controller Manager中的Replication Controller(副本控制器)和资源对象Replication Controller,我们将资源对象简写为RC,而Replication Controller特指“副本控制器”。Replication Controller的核心作用是确保在任何时间集群中一个RC所关联的pod都保持一定数量的pod副本处于正常运行状态。如果该类pod的pod副本数量太多,则Replication Controller会销毁一些pod副本;反之Replication Controller会添加pod副本,直到该类pod的pod副本数量达到预设的副本数量。最好不要超过RC直接创建pod,因为Replication Controller会通过RC管理pod副本,实现自动创建、补足、替换、删除pod副本,这样就能提高系统的容灾能力,减少由于节点崩溃等意外状况造成的损失。即使应用程序只用到一个pod副本,也强烈建设使用RC来定义pod。Replication Controller管理的对象是pod,因此其操作与pod的状态及重启策略息息相关。副本控制器的常用使用模式:(1)重新调度:不管想运行1个副本还是1000副本,副本控制器能够确保指定pod数量的副本存在于集群中,如果节点故障或副本被终止运行等意外情况,将会重新调度直到达到预期的副本正常运行。(2)弹性伸缩:手动或通过自动扩容代理修改副本控制器的spec.replicas属性值,非常容易实现扩大或缩小副本的数量。(3)滚动更新:副本控制器被设计成通过逐个替换pod的方式来辅助服务的滚动更新。推荐的方式是创建一个新的只有一个副本的RC,若新的RC副本数量加1,则旧的RC的副本数量减1,直到这个旧的RC副本数量为零,然后删除该旧的RC。在滚动更新的讨论中,我们发现一个应用在滚动更新时,可能存在多个版本的release。事实上,在生产环境中一个已经发布的应用程序存在多个release版本是很正常的现象。通过RC的标签选择器,我们能很方便地实现对一个应用的多版本release的跟踪。node controllerNode Controller负责发现、管理和监控集群中的各个node节点。Kubelet在启动时通过API Server注册节点信息,并定时向API Server发送节点信息。API Server接收到这些信息后,将这些信息写入etcd。存入etcd的节点信息包括节点健康状况、节点资源、节点名称、节点地址信息、操作系统版本、docker版本、kubelet版本等。节点健康状况包含“就绪(true)”、“未就绪(false)”和“未知(unknown)”三种。(1)Controller Manager 在启动时如果设置了—cluster-cidr参数,那么为每个没有设置spec.podCIDR的node生成一个CIDR地址,并用该CIDR设置节点的spec.PodCIDR属性,这样的目的是防止不同节点的CIDR地址发生冲突。(2)逐个读取节点的信息,多次尝试修改nodeStatusMap中的节点状态信息,将该节点信息和node controller的nodeStatusMap中保存的节点信息比较。如果判断中没有收到kubelet发送的节点信息、第一次收到节点kubelet发送的节点信息,或在该处理过程中节点状态变成非“健康”状态,则在nodeStatusMap中保存该节点的状态信息,并用node controller所在节点的系统时间作为探测时间和节点状态变化时间。 如果判断出在某一段时间内没有收到节点的状态信息,则设置节点状态为“未知(unknown)”,并且通过api server保存节点状态。(3)逐个读取节点信息,如果节点状态变为非“就绪”状态,则将节点加入待删除队列,否则将节点从该队列中删除。如果节点状态为非“就绪”状态,且系统指定了Cloud Provider,则node controller调用Cloud Provider查看节点,若发现并节点故障,则删除etcd中的节点信息,并删除和该节点相关的pod等资源的信息。ResourceQuota controller作为容器集群的管理平台, kubernetes也提供了资源配额管理这一高级功能,资源配额管理确保指定的对象在任何时候都不会超量占用系统资源,避免了由于某些业务进程的设计或实现的缺陷导致整个系统运行紊乱甚至意外宕机,对整个集群的平稳运行和稳定性有非常重要的作用。目前kubernetes支持三个层次的资源配额管理:(1)容器级别,可以对CPU和内存的资源配额管理。(2)pod级别,可以对pod内所有容器的可用资源进行限制。(3)namespace级别,为namespace(可以用于多租户)级别的资源限制,包括:pod数量、replication Controller数量、service数量、ResourceQuota数量、secret数量、可持有的PV(persistent volume)数量。 kubernetes的配额管理是通过准入机制(admission control)来实现的,与配额相关的两种准入控制器是LimitRanger和ResoureQuota,其中LimitRanger作用于pod和container上,ResourceQuota则作用于namespace上。此外,如果定义了资源配额,则scheduler在pod调度过程中也会考虑这一因素,确保pod调度不会超出配额限制。典型的资源控制流程如下图所示:namaspace controller用户通过API Server可以创建新的namespace并保存在etcd中,namespace controller定时通过api server读取这些namespace信息。如果namespace被API标识为优雅删除(设置删除期限,deletionTimestamp属性被设置),则将该namespace的状态设置为“terminating”并保存到etcd中。同时namespace controller删除该namespace下的serviceAccount、RC、Pod、Secret、PersistentVolume、ListRange、SesourceQuota和event等资源对象。 当namespace的状态被设置为“terminating”后,由Adminssion Controller的NamespaceLifecycle插件来阻止为该namespace创建新的资源。同时,在namespace controller删除完该namespace中的所有资源对象后,Namespace Controller对该namespace执行finalize操作,删除namespace的spec.finalizers域中的信息。如果Namespace Controller观察到namespace设置了删除期限(即DeletionTimestamp属性被设置),同时namespacer 的spec.finalizers域值是空的,那么namespace controller将通过API Server删除该namespace资源。kubernetes安全控制ServiceAccount Controller和token Controller是与安全相关的两个控制器。ServiceAccount Controller在Controller Manager启动时被创建。它监听Service Account的删除事件和Namespace的创建、修改事件。如果在该Service Account的namespace中没有default Service Account,那么ServiceAccount Controller为该Service Account的namespace创建一个default ServiceAccount。在API Server的启动中添加“—admission_control=ServiceAccount”后,API Server在启动时会自己创建一个key和crt(/var/run/kubernetes/apiserver.crt和apiserver.key),然后在启动./kube-controller-manager时添加参数service_account_privatge_key_file=/var/run/kubernetes/apiserver.key,这样启动kubernetes master后,就会发现在创建Service Account时系统会自动为其创建一个secret。如果Controller Manager在启动时指定参数为service-account-private-key-file,而且该参数所指定的文件包含一个PEM-encoded的编码的RSA算法的私钥,那么,Controler Manager会创建token controller对象。Token controllertoken controller对象监听Service Account的创建、修改和删除事件,并根据事件的不同做不同的处理。如果监听到的事件是创建和修改Service Account事件,则读取该Service Account的信息;如果该Service Account没有Service Account Secret(即用于访问Api server的secret),则用前面提及的私钥为该Service Account创建一个JWT Token,将该Token和ROOT CA(如果启动时参数指定了该 ROOT CA)放入新建的secret中,将该新建的secret放入该Service Account中,同时修改etcd中Service Account的内容。如果监听到的事件是删除Service Account事件,则删除与该Service Account相关的secret。token controller对象同时监听secret的创建、修改和删除事件,并根据事件的不同做不同的处理。如果监听到的事件是创建和修改secret事件,那么读取该secret中annotation所指定的Service Account信息,并根据需要为该secret创建一个和其Service Account相关的token;如果监听到的事件是删除secret事件,则删除secret和相关的Service Account的引用关系。service controller&endpoint controllerKubernetes service是一个定义pod集合的抽象,或者被访问都看作一个访问策略,有时也被称为微服务。 kubernetes中的service是种资源对象,各所有其他资源对象一样,可以通过API Server的POST接口创建一个新的实例。在下面的例子代码中创建了一个名为“MyServer”的Service,它包含一个标签选择器,通过该标签选择器选择所有包含标签为“app=MyApp”的pod作为该service的pod集合。Pod集合中的每个pod的80端口被映射到节点本地的9376端口,同时kubernetes指派一个集群IP(即虚拟IP)给该service。{ “kind”: ”service”, “apiVersion”: ”v1”, “metadata”: { “name”: ”MyService” }, “spec”: { “selector”: { “app”: ”MyApp” }, “ports”: [ { “protocol”: ”TCP”, “port”: 80, “targetPort”: 9376 } ] },}四.功能特性Service 集群访问流程(服务发现)在kubernetes集群中的每个node上都运行着一个叫“kube-proxy”的进程,该进程会观察master节点添加和删除“service”和“endpoint”的行为,如图中第1步所示。kube-proxy为每个service在本地主机上开一个端口(随机选择)。任何访问该端口的连接都被代理到相应的一个后端pod上。Kube-proxy根据round robin算法及service的session粘连(SessionAffinity)决定哪个后台pod被选中,如第2步所示。最后,如第3步所示,kube-proxy在本机的iptables中安装相应的规则,这些规则使得iptables将捕获的流量重定向到前面提及的随机端口。通过该端口流量再被kube-proxy转到相应的后端pod上。在创建了服务后,服务endpoint模型会创建后端pod的IP和端口列表(包含中endpoint对象中),kube-proxy就是从这个endpoint列表中选择服务后端的。集群内的节点通过虚拟IP和端口能够访问service后台的pod。在默认情况下,kubernetes会为server指定一个集群IP(或虚拟IP、cluster IP),但在某些情况下,希望能够自己指定该集群IP。为了给service指定集群IP,用户只需要在定义service时,在service的spec.clusterIP域中设置所需要的IP地址即可。Scheduler(调度)scheduler在整个kubernetes系统中承担了“承上启下”的重要功能,“承上”是指它负责接收Controller Manager创建的新pod,为其安排一个落脚的“家”——目标node;“启下”是指安置工作完成后,目标node上的kubelet服务进程接管后继工作,负责pod生命周期中的“下半生”。具体来说,scheduler的作用是将待调度的pod(API新创建的Pod、Controller Manager为补足副本而创建的pod等)按照特定的调度算法和调度策略绑定(binding)到集群中的某个合适的node上,并将绑定信息写入etcd中。在整个调度过程中涉及三个对象,分别是:待调度的pod列表、可用node列表、以及调度算法和策略。简单地说,就是通过调度算法调度,为待调度pod列表中的每个pod从node列表中选择一个最适合的node。随后,目标node上的kublet通过API Server监听到scheduler产生的pod绑定事件,然后获对应的取pod,下载image镜像,并启动容器。Scheduler(调度策略)scheduler当前提供的默认调度流程分为两步:(1)预选调度过程,即遍历所有目标node,筛选出符合要求的候选节点。为此kubernetes内置了多种预先策略(xxx predicates)供用户选择。(2)确定最优节点,在第一步的基础上,采用优先策略(xxx priority)计算出每个候选节点的积分,积分最高都胜出。 scheduler的调度流程是通过插件方式加载的“调度算法提供者(AlgorithmProvider)”具体实现的。一个AlgorithmProvider其实就是包括了一组预选策略与一组优选策略的结构体,注册AlgorithmProvider的函数如下: func RegisterAlgorithmProvider(name string, predicateKeys, priorityKeys util.StringSet) 它包含3个参数,name string 参数为算法名;predicateKeys参数为算法集合用到的预选策略集合 priorityKeys 参数为算法用到的优选策略集合 scheduler 中可用的预选策略包含7个,每个节点只有通过PodFitsPorts、PodFitsResources、NoDiskConflict、PodSelectorMatches、PodFitsHost 5个默认预先策略后,才能初步被选中,进入下一个流程。每个节点通过优选策略时都会算出一个得分,计算各项得分,最终选出得分值最大的节点作为优选的结果(也是调度算法的结果)。LeastRequestedPriority,选择资源消耗最小节点:(1)计算出所有备选节点上运行的pod和备选pod的CPU占用量totalMilliCPU(2)计算出所有备选节点上运行的pod和备选pod的内存占用量totalMomory(3)计算每个节点的得分,计算规则大致如下: score=int(((nodeCpuCapacity-totalMilliCPU)10)/nodeCpuCapacity+((nodeMemoryCapacity-totalMemory)10/nodeMemoryCapacity)/2) CalculateNodeLabelPriority,根据CheckNodeLabelPresence策略打分 BalancedResourceAllocation,选择资源使用最均衡节点(1)计算出所有备选节点上运行的pod和备选pod的CPU占用量totalMilliCPU(2)计算出所有备选节点上运行的pod和备选pod的内存占用量totalMomory(3)计算每个节点的得分,计算规则大致如下: score=int(10-math.abs(totalMilliCPU/nodeCpuCapacity-totalMemory/nodeMemoryCapacity) * 10)节点管理节点管理包含节点的注册、状态上报、Pod管理、容器健康检查、资源监控等部分。节点注册在kubernetes集群中,在每个node节点上都会启动一个kubelet服务进程。该进程用于处理master节点下发到本节点的任务,管理pod及pod中的容器。每个kubelet进程会在API Server上注册节点自身信息,定期向master节点汇报节点资源使用情况,并通过cAdvisor监控容器和节点资源。节点通过设置kubelet的启动参数“—register-node”,来决定是否向API Server注册自己。如果该参数为true,那么kubelet将试着通过API Server注册自己。作为自注册,kubelet启动还包含下列参数:–api-servers,告诉kubelet API Server的位置; –kubeconfig,告诉kubelet在哪儿可以找到用于访问API Server的证书; –cloud-provider,告诉kubelet如何从云服务商(IAAS)那里读取到和自己相关的元数据。状态上报kubelet在启动时通过API Server注册节点,并定时向API Server发送节点新消息,API Server在接收到这些信息后,将这些信息写入etcd。通过kubelet的启动参数“—node-status-update-frequency”设置kubelet每隔多少时间向API Server报告节点状态,默认为10秒。Pod管理kubelet通过以下几种方式获取自身node上所要运行的pod清单: (1)文件:kubelet启动参数“–config”指定的配置文件目录下的文件。通过—file-check-frequency设置检查该文件目录的时间间隔,默认为20秒。(2)HTTP端点(URL):通过“—manifest-url”参数设置。通过—http-check-frequency设置检查该HTTP端点的数据时间间隔,默认为20秒。(3)API Server:kubelet通过API Server监听etcd目录,同步pod清单。所有以非API Server方式创建的pod都叫作static pod。Kubelet将static pod的状态汇报给API Server,API Server为static pod创建一个mirror pod和其相匹配。Mirror pod的状态将真实反映static pod的状态。当static pod被删除时,与之相对应的mirror pod也会被删除。Kubelet通过API Server client使用watch+list的方式监听“/registry/node/<当前node名称>”和“/registry/pods”目录,将获取的信息同步到本地缓存中。kubelet监听etcd,所有针对pod的操作将会被kubelet监听到。如果发现有新的绑定到本节点的pod,则按照pod清单的要求创建该pod。如果发现本地的pod被修改,则kubelet会做出相应的修改,如删除pod中的某个容器时,则通过docker client删除该容器。如果发现删除本节点的pod,则删除相应的pod,并通过docker client删除pod中的容器。kubelet读取监听到的信息,如果是创建和修改pod任务,则做如下处理:(1)为该pod创建一个数据目录。(2)从API Server读取该pod清单。(3)为该pod挂载外部卷(Extenal Volume)。(4)下载pod用到的secret。(5)检查已经运行在节点中的pod,如果该 pod没有容器或pause容器没有启动,则先停止pod里所有容器进程。如果在pod中有需要删除的容器,则删除这些容器。(6)用“kubernetes/pause”镜像为每个pod创建一个容器,该pause容器用于接管pod中所有其他容器的网络。(7)为pod中的每个容器做如下处理:为容器计算一个hash值,然后用容器的名字去docker查询对应容器的hash值。若查到容器,且两者hash值不同,则停止docker中容器进程,并停止与之关联的pause容器进程;若两者相同不做任何处理。如果容器被中止了,且容器没有指定的restartPolicy(重启策略),则不做任何处理。调用docker client下载容器镜像,调用docker client运行容器。容器健康检查pod通过两类探针来检查容器的健康状态。一个是LivenessProbe探针,用于判断容器是否健康,告诉kubelet一个容器什么时候处于不健康的状态。如果LivenessProbe探针探测到容器不健康,则kubelet将删除容器,并根据容器的重启策略做相应的处理。如果一个容器不包含LivenessProbe探针,那么kubelet认为该容器的LivenessProbe探针返回的值永远是“success”.另一类是ReadinessProbe探针,用于判断容器是否启动完成,且准备接收请求。如果ReadinessProbe探针检测到失败,则pod的状态将被修改。Endpoint controller将从service的endpoint中删除包含该容器所在pod的IP地址的endpoint条目。参考《kubernetes权威指南》此文已由作者授权腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号 ...

January 7, 2019 · 2 min · jiezi

基于Kubernetes 的机器学习工作流

介绍Pipeline是Kubeflow社区最近开源的一个端到端工作流项目,帮助我们来管理,部署端到端的机器学习工作流。Kubeflow 是一个谷歌的开源项目,它将机器学习的代码像构建应用一样打包,使其他人也能够重复使用。 kubeflow/pipeline 提供了一个工作流方案,将这些机器学习中的应用代码按照流水线的方式编排,形成可重复的工作流。并提供平台,帮助编排,部署,管理,这些端到端机器学习工作流。概念pipeline 是一个面向机器学习的工作流解决方案,通过定义一个有向无环图描述流水线系统(pipeline),流水线中每一步流程是由容器定义组成的组件(component)。 当我们想要发起一次机器学习的试验时,需要创建一个experiment,在experiment中发起运行任务(run)。Experiment 是一个抽象概念,用于分组管理运行任务。Pipeline:定义一组操作的流水线,其中每一步都由component组成。 背后是一个Argo的模板配置。Component: 一个容器操作,可以通过pipeline的sdk 定义。每一个component 可以定义定义输出(output)和产物(artifact), 输出可以通过设置下一步的环境变量,作为下一步的输入, artifact 是组件运行完成后写入一个约定格式文件,在界面上可以被渲染展示。Experiment: 可以看做一个工作空间,管理一组运行任务。Run: pipeline 的运行任务实例,这些任务会对应一个工作流实例。由Argo统一管理运行顺序和前后依赖关系。Recurring run: 定时任务,定义运行周期,Pipeline 组件会定期拉起对应的Pipeline Run。Pipeline 里的流程图组件的Artifact模块Pipeline 的组件比较简单,大致分为5个部分。MySQL: 用于存储Pipeline/Run 等元数据。Backend: 一个由go编写的后端,提供kubernetes ApiServer 风格的Restful API。处理前端以及SDK发起的操作请求。 Pipeline/Experiment 之类的请求会直接存入MySQL元数据。和Run 相关的请求除了写入MySQL以外还会通过APIServer 同步操作Argo实例。CRD Controller: Pipeline 基于Argo扩展了自己的CRD ScheduledWorkflow, CRD Controller 中会主要监听ScheduledWorkflow和Argo 的Workflow 这两个CRD变化。处理定期运行的逻辑。Persistence Agent: 和CRD Controller 一样监听Argo Workflow变化,将Workflow状态同步到MySQL 元数据中。它的主要职责是实时获取工作流的运行结果。Web UI:提供界面操作。 从Backend 中读取元数据,将流水线过程和结果可视化,获取日志,发起新的任务等。其他工具除了以上核心模块以外, Pipeline提供了一系列工具,帮助更好构建流水线。SDK, 用于定义pipeline和component,编译为一个argo yaml模板,可以在界面上导入成pipeline。CLI 工具,替代Web UI,调用Backend Api 管理流水线Jupyter notebook。 可以在notebook中编写训练代码,也可以在notebook中通过sdk管理Pipeline。本文作者:萧元阅读原文本文为云栖社区原创内容,未经允许不得转载。

January 3, 2019 · 1 min · jiezi

KubeCon 2018 参会记录 —— FluentBit Deep Dive

在最近的上海和北美KubeCon大会上,来自于Treasure Data的Eduardo Silva(Fluentd Maintainer)带来了最期待的关于容器日志采集工具FluentBit的最新进展以及深入解析的分享;我们知道Fluentd是在2016年底正式加入CNCF,成为CNCF项目家族的一员,其被广泛用于容器集群中进行应用日志的采集、处理和聚合,但今天主要是跟大家分享一下同样来自于Treasure Data新开源的日志采集工具——FluentBit。FluentBit vs Fluentd既然已经有了Fluentd,那么为什么还要开发一个FluentBit呢?我们知道,Fluentd是基于Ruby语言的,在一些应用日志量较大或者单节点日志量较大的场景下,通过Fluentd采集日志的速率会远落后于应用日志的产生速率,进而导致日志采集的延迟时间较大,这对于一些实时性要求较高的业务系统或者监控系统来说是不可接受的;另外一方面,也是由于Fluentd自身的日志处理逻辑越来越复杂,全部放置在一个组件里来完成会导致越来越臃肿,因此Treasure Data在基于Fluentd优秀的架构和设计理念上重新开发了一个更加轻量级、更加高性能的日志采集工具——FluentBit,其主要采用C语言进行开发。从上面我们可以清晰地看到FluentBit本身占用的内存资源会比Fluentd少很多,且基本没有其他额外的环境依赖,但是支持的插件数相较于Fluentd会少很多,需要时间来慢慢丰富。FluentBit WorkflowFluentBit 内置了一个Service Engine,其每采集到一条日志时都会执行从Input到Output的整个Action Chain:- Input日志数据入口,FluentBit支持多种不同数据来源类型的Input Plugin,不仅能采集容器日志、内核日志、syslog、systemd日志,还支持通过TCP监听接收远程客户端的日志,同时还能够采集系统的CPU、内存和DISK的使用率情况以及本机Network流量日志。- Parser通过情况下我们的应用日志都是非结构化的,那么Parser主要是负责将采集到的非结构化日志解析成结构化的日志数据,一般为JSON格式;FluentBit 默认已经预置了下面几种Parser:JSON:按照JSON格式来进行日志数据解析;Regex:依据配置的正则表达式来进行日志数据解析;Apache:遵循Apache日志格式来进行解析;Nginx:遵循Nginx日志格式来进行解析;Docker:遵循Docker标准输出日志格式进行解析;Syslog rfc5424:按照syslog rfc5424规范格式进行日志解析;Syslog rfc3164:按照syslog rfc3164规范格式进行日志解析;- Filter在实际的生产应用中,我们通常需要对采集到的应用日志记录进行修改或者添加一些关键信息,这都可以Filter Plugin来完成;目前FluentBit也已预置了多种Filter插件:Grep:允许匹配或者过滤掉符合特定正则表达式的日志记录;Record Modifier:允许对日志数据进行修改或者添加新的KV数据,通过此可以方便我们对日志数据进行打标;Throttle:支持采用漏桶和滑动窗口算法进行日志采集速率控制;Kubernetes:自动提取容器或者POD相关信息并添加到日志数据中;Modify:基于设置的规则来对日志数据进行修改;Standard Output:允许将日志数据直接打印到标准输出;Lua:支持通过嵌入Lua Script来修改添加日志数据;- BufferFluentBit 内部本身提供了Buffer机制,会将采集到的日志数据暂存在Memory中直到该日志数据被成功路由转发到指定的目标存储后端。- Routing路由是FluentBit的一个核心功能,它允许我们配置不同的路由规则来将同一条日志数据记录转发到一个或多个不同的接收后端,其内部主要是基于每条日志数据的Tag来进行路由转发,同时支持正则匹配方式;如下面配置则表示希望将Tag满足正则表达式my_的日志直接打印到标准输出中:[INPUT] Name cpu Tag my_cpu[INPUT] Name mem Tag my_mem[OUTPUT] Name stdout Match my_- OutputOutput 主要是用来配置采集到的日志数据将要被转发到哪些日志存储服务中,目前已支持多种主流的存储服务,如ElasticSearch、NATS、InfluxDB、Kafka、Splunk、File、Console等,同样也支持将日志数据继续通过HTTP(S)协议将其传输到其他服务接口中;另外这里有一个比较特殊的Output就是Fluentd,可能大家会比较奇怪,其实在未来的日志架构模型中,FluentBit主要是在采集端专职负责日志的高性能采集,然后可以将采集到的日志在Fluentd中进行较复杂的聚合处理(同Filebeat和Logstash):Other FeaturesEvent Driven内置的Service Engine采用完全异步的事件驱动模型来进行日志的采集和分发。Configuration简单灵活的、高可读性的配置方式,FluentBit的Workflow模型可完全通过配置文件的方式清晰制定。Upstream Manager采用统一的日志上游服务的网络连接管理,包括Keepalive和IO Error处理。TLSv1.2 / Security对于安全敏感的日志数据,支持通过TLS加密通道进行日志传输。Upcoming FeaturesFilesystem buffering mode当前FluentBit只支持Memory的buffer方式,但考虑到内存的易失性,未来也将会支持基于Filesystem的buffer机制。Optional plugins as shared libraries未来会将一些已内置的但又不是必需的插件以共享链接库的方式来进行动态加载。Kubernetes Filter improvements未来会继续深度整合Kubernetes,通过API获取更多POD关键信息并自动添加到日志数据记录中。Summary这两次的KubeCon大会上Eduardo Silva对日志采集工具FluentBit都进行了深度的解析分享,不仅介绍了FluentBit的整个架构模型,而且还分享了未来的发展方向,从整个分享来看FluentBit会侧重在日志的高性能采集方面;而阿里云容器服务在2017年初开源的Log-Pilot:https://github.com/AliyunContainerService/log-pilot ,其不仅能够采集容器的标准输出日志,而且还能动态地发现采集容器内文件日志,同时支持简单高效的日志声明式配置、支持日志路由、日志数据打标以及多种日志采集插件,未来我们将进一步与社区紧密结合,整合FluentBit的高性能采集特性以及Log-Pilot的动态发现和声明式配置优势来进一步增强容器化应用日志的配置采集效率。本文作者:chenqz阅读原文本文为云栖社区原创内容,未经允许不得转载。

December 24, 2018 · 1 min · jiezi

在Kubernetes上运行区块链服务(BaaS)

笔者注:本文是在2018年11月15日由Linux基金会CNCF主办的KubeCon & CloudNativeCon China 2018大会的“Running Blockchain as a Service (BaaS) on Kubernetes”演讲内容基础上整理而成,从技术上介绍了阿里云如何将基于区块链Hyperledger Fabric的BaaS和容器集群技术Kubernetes进行结合的设计理念和实践经验分享。大家好!我是来自于阿里云区块链团队的余珊,今天给大家分享的是《在Kubernetes上运行区块链服务(BaaS)》这个主题。以上是今天分享的内容大纲,其中重点在第三部分,我们将对BaaS结合Kubernetes的一些典型问题进行深入探讨。首先我们分享一下在我们眼中的区块链和BaaS的定义是什么。从狭义上来说,区块链是一种分布式共享账本技术,基于智能合约,在各参与方之间达成对交易的共识,并实现账本交易历史的不可篡改。这个定义是大家所熟知的,并且是从技术和功能上进行的概括。而从广义上来说,我们认为,区块链也是一种在机构、个人、机器之间,构建分布式信任网络、连接可信数据、实现价值流动的新的架构和协作模式。这也是跳出技术和功能维度,从更高维度去进行的理解和总结。对于另一个概念"BaaS",即"Blockchain as a Service", 我们认为,是云平台之上的区块链平台服务,提供了区块链系统的部署、运维、治理能力,以及提供了区块链应用运行和管理的能力。区块链从其类型上可分为私有链、公有链、联盟链三种类型,而从系统拓扑上我们可以将其对应为下述三种模式。对于传统的中心化系统或私有链来说,它基本属于一种星型中心化系统。对于公有链来说,是一种将所有参与企业和个人都对等连接在一起的完全去中心化系统。而对于联盟链来说,是一种带分层结构的多中心化系统。而阿里云今天主要关注的是面向企业场景的联盟链技术类型。下面我们来探讨一下为什么将区块链与容器技术以及Kubernetes进行结合。首先,我们来分析一下区块链的特点。我们将其分为区块链系统和区块链业务应用两类。区块链系统是以数据为核心、高度分布式、Full-Mesh网络拓扑、Long-Running、复杂系统类型。数据为核心:其中最重要的是账本上的数据。高度分布式:因为区块链节点可能部署于不同机房、不同region、不同国家等等。Full-Mesh: 区块链节点之间要依赖全连通的网络以实现共识、账本同步等过程。Long-Running:区块链服务和节点是长时间运行,而不是像Web应用或批处理任务那样短生命周期的。复杂系统类型:区块链系统不是一两个模块构成的简单应用,而是往往一整天解决方案或系统的形式。区块链业务应用:没有统一的标准,可能包含各种应用类型,包括无状态应用、有状态应用、Web应用、数据型应用等等类型。接下来,我们分析一下区块链结合容器技术会带来哪些优势:容器技术为区块链系统和业务应用提供了标准化的软件打包、分发的能力。容器技术实现了区块链运行环境的一致性,以及与底层基础架构的解耦,使得区块链系统和业务应用可以很方便地移植和运行在各种平台之上。进一步的,我们发现,区块链使用Kubernetes集群技术可获得以下几方面的优势:Kubernetes提供了灵活的区块链所需要的底层资源的调度能力,如计算、存储、网络等。Kubernetes强大的运维管理能力,使得我们的区块链服务的产品上线速度以及运维的效率大大提升。Kubernetes支持各种应用类型以及微服务架构,因此对于上面区块链系统和区块链业务应用各自的需求都能很好地满足。使用Kubernetes,可以更好地跟云平台进行集成,因为今天在业界它已经成为了各大云厂商云原生应用的标准底座了。Kubernetes还提供了丰富的安全和隔离功能,这对我们区块链的安全防护体系是很大的增强。另外,围绕Kubernetes有着非常活跃的社区和丰富的技术和业务生态,因此为结合区块链的研发提供了强大的技术支持和资源。这里解答图中的一个疑问,微服务架构是否适合区块链,这要结合上面的区块链特点分析来看待:对区块链系统来说,内部组件之间是强耦合、强依赖的关系,比较难解耦,内部各组件本身不是通用化的服务定位,也不是REST化服务接口,更多是例如gRPC调用,因此不是太适合微服务架构。但是对区块链业务应用来说,则很适合考虑与微服务架构进行结合。上面这幅图展示了阿里云区块链产品形态的演进历史,同时也可以看出我们在区块链结合容器以及Kubernetes方面所在的工作。在2017年10月,我们开始提供基于容器服务的区块链解决方案,支持Hyperledger Fabric,为企业提供一键式的区块链自动部署能力,当时是基于Docker Swarm集群技术。紧接着在2017年12月,我们推出了支持Kubernetes的容器服务区块链解决方案,并且在业界也是较早开始使用Helm Chart部署区块链的。在今年7月底,我们正式推出了阿里云区块链服务BaaS,支持Hyperledger Fabric,同样也是基于Kubernetes。而在今年9月杭州云栖大会上,阿里云BaaS也正式支持了蚂蚁区块链,在这背后蚂蚁区块链也通过适配改造工作实现了在Kubernetes上的部署运行。这一页展示的是阿里云BaaS的产品架构大图。其中最核心的是BaaS,目前已经支持Hyperledger Fabric和蚂蚁区块链。它们的运行实例底座都是基于阿里云容器服务Kubernetes集群。今天的演讲内容主要是围绕Hyperledger Fabric跟Kubernetes结合这方面展开讨论的。上面这一页展示了阿里云容器服务Kubernetes版的产品架构图。这里我们展示了一套跨region的Hyperledger Fabric联盟链的部署架构图。在联盟管理方的Kubernetes集群上部署了Orderer organization和Peer Organization, 而在其他业务参与方所在region的Kubernetes上部署了各自的Peer Organization. 这里的CA、Peer、Orderer、Kafka、ZooKeeper的每个实例都是用了Kubernetes的Service和Deployment类型对象来定义。此外区块链的业务应用可以部署在Kubernetes上或者其他环境上,通过SLB映射到集群worker节点的NodePort上,来访问区块链的各个service。接下来我们进入重点的第三部分,对于实现BaaS运行在Kubernetes的过程,我们曾经遇到的一些有代表性的问题,以及我们的解决思路和实践经验。首先是关于区块链BaaS的打包、发布、服务编排等方面的问题。对于以Hyperledger Fabric为代表的区块链系统来说,这方面面临的主要问题是:区块链系统本身较为复杂,在一套典型部署里可能涉及到三十多个容器、近二十个服务、十来个容器镜像;而且这些服务相互之间有较强的依赖。对于这个问题,我们的解决思路是:在打包部署方面,从一开始我们便选用了容器镜像以及Kuberentes的Helm Chart作为标准的格式和工具。这里尤其提一下,为了保证联盟链各组织创建的独立性和灵活性,我们采用的是一类组织(例如Orderer Org或Peer Org)使用一套chart的方式。在存储库管理方面,目前使用的是阿里云OSS作为Chart Repo(当然可以使用功能更丰富的如ChartMuseum等工具),使用阿里云容器镜像服务作为镜像仓库。这里我们还采用了region化的镜像仓库配置,加快大体积镜像的下载速度,同时采用imagePullSecret保护镜像的安全。在配置方式方面,我们采用了Kubernetes的ConfigMap和Secrets来存储主要的配置和安全信息,以及使用Chart Values来让管控可以根据客户的输入去定制BaaS联盟链实例。在服务编排方面,为了满足服务的依赖性需求,我们结合了Chart Template,Chart的Hook(钩子)机制,以及Kubernetes的Init Container加上Shell脚本方式,实现各种服务尤其在启动过程中的依赖和顺序保证。对于企业来说,业务系统的高可用性是非常重要的,尤其是对生产环境的系统运行和应用访问。这里我们分享一下在BaaS的每一个层面上的高可用设计思路,以及Kubernetes在其中起到怎样的帮助。首先在云基础架构和云资源服务层,我们通过云数据中心的多可用区、所依赖的云服务本身的高可用性和高可靠性来提供保障。在BaaS管控层,通过管控组件的多实例化部署避免单点故障。在容器服务的Kubernetes集群,采用3个master节点和多个worker节点的方式提供应用底座的高可用。在Hyperledger Fabric这一层,它的Orderer、Peer、Kafka、ZooKeeper、CA等类型节点均有集群或高可用互备的设计,比如任一节点挂掉的话,其他节点依然能正常提供服务。但这里有一个关键的点,就是在Kubernetes集群上部署的时候,为了避免这些本应高可用互备的Fabric节点的pod被调度到同一个worker node上,我们采用了Kubernetes Pod Anti-Affinity的功能区将高可用集群的pod调度到不同的worker上,这样保证了真正高可用的部署,提高了对故障的容忍度。在区块链业务应用层,则需要各个企业客户对应用进行周全的高可用设计和实现。在运行时,应用访问Fabric各个服务的这一环节,我们BaaS内置了云平台的SLB负载均衡能力(包含对服务端口的健康检查),以及Fabric的Service Discovery,来保证即使后端部分节点或服务不可用时,应用的调用链路都会被调度到可用的节点或服务上。下面我们谈谈BaaS数据持久化存储的问题。虽然上面已经介绍了BaaS的高可用性设计,但我们仍需考虑如何将链上账本数据、区块链关键配置等重要内容持久化保存到可靠的外部存储而不是容器内部,这样便可以在服务器节点全部发生故障,或者系统重启、备份恢复等场合依然可以实现对系统和数据的恢复。首先,作为背景,我们分析了如果使用本地盘方式可能存在的问题:Kubernetes本身对pod的调度默认并没有限定worker节点,因此如果使用本地盘,就会因为在重启或恢复过程中调度导致的pod漂移而无法读取原来worker节点上的本地盘。对于第一个问题,Kubernetes提供了NodeSelector的机制可以让pod可以绑定worker节点进行部署,不会调度到其他worker节点上,这样就可以保证能始终访问到一个本地盘。但这又带来另一个问题,即在容灾的场景,如果这个节点包括其本地盘受到损坏无法恢复时,会导致整个pod也无法恢复。因此,我们在设计BaaS中的选择是阿里云的NAS文件系统存储、以及阿里云的云盘。在数据可靠性方面,NAS和云盘可以分别提供99.999999999%和99.9999999%的数据可靠性。此外,我们都选用了SSD类型以保证I/O性能。在Kubernetes部署的时候,Fabric的节点通过Persistent Volume和Persistent Volume Claim挂载上相应的存储,并且这些存储是为每一个Fabric的业务organization独立分配的,保证了业务数据的隔离性。在和参加KubeCon大会的一些区块链用户交流的时候,有朋友提到随着账本数据的持续增长,可以怎样解决存储问题。在我们的实践中,我们发现阿里云的NAS有一些很适合区块链账本存储的一些特点:首先,阿里云NAS可提供存储容量动态无缝扩容,在这过程中Fabric节点或区块链业务应用均无需重启或停机,对存储扩容过程完全无感知。其次,有用户担心随着存储数据量的增大,存储的性能是否会明显下降。恰恰相反的是,在阿里云NAS随着所使用的数据量变得越大,NAS所提供的吞吐性能会变得更高,这样可以打消企业用户在长期生产运行方面的顾虑。在上图的右边是Fabric不同类型的区块链节点使用不同类型存储的一个示意图。接下来我们探讨一下在设计搭建BaaS联盟链跨企业的网络方面遇到的挑战。对于大多数区块链技术而言,包括Hyerpedger Fabric, 在网络上要求区块链节点之间形成Full Mesh全连通网络,以实现节点间的账本数据同步、区块广播、节点发现、交易背书等过程,同时企业也要求保障跨企业链路的安全性。对于这些需求,我们梳理了业界目前常见的几类解决方案如下,并进一步分析它们存在的一些不足之处。方案一是采用单一VPC的联盟链网络方案,在这种模式下,联盟链的所有区块链节点均被部署到一套Kubernetes集群网络或VPC内。这种方案实质上是一种私有链的模式,失去了联盟链的各方自治的价值。方案二是基于公网的联盟链网络方案,区块链节点分布式部署在不同区域,通过公网IP对外提供服务以及实现互相通信。这种方案可以较灵活、较低成本低满足大多数基本需求,但对于高级需求如企业级安全网络则不能很好地满足。方案三是基于专线互联的联盟链网络方案,它采用运营商专线方式将不同网络或数据中心进行两两互联,在业界一些企业中得到了采用。但这里面主要存在两方面的问题,首先是如果联盟链参与企业采用了不同电信运营商的专线方案的话,项目实施的复杂性和成本都会很高;其次,如果几家企业已经建好了这样一个联盟网络,对于新来的参与企业,它的接入复杂度和成本也是一个不小的问题。针对上述各种问题,我们在阿里云BaaS基础之上,结合了CEN云企业网,提供了一种安全的联盟链网络方案,主要面向高端需求的企业用户。方案的核心,是采用CEN云企业网打通不同企业的VPC网络,就像一张跨企业的环网,这样不同企业不同的VPC内的网络就可以在CEN内实现全连通。在实现网络连通之后,因为阿里云BaaS联盟链中的Peer,Orderer,CA等服务是通过域名来访问的,目的是提升应用访问的灵活性,而在CEN的这套方案中,我们可以结合云解析PrivateZone,来实现企业环网内各企业VPC之间的统一域名解析。而且上述的网络连通性和域名解析仅限于联盟内部,不会暴露到外网。除了在公共云环境之外,对于那些将区块链节点部署于本地IDC的企业来说,他们也可以通过VPN或者专线方式,接入到云上已和CEN打通的任一VPC中,便可实现和联盟任意节点的通信。作为一个小提醒,在方案实施环节,需要注意提前规划好不同VPC内的内网地址分配,避免在环网中发生冲突。这样我们便形成了一套真正跨企业、跨账户,打通各个VPC的安全联盟链网络方案。下面我们将探讨一个非常有挑战性的问题。众所周知,智能合约是区块链的一个核心。Hyperledger Fabric中的智能合约即chaincode是运行于容器当中。在上面这幅图里我们总结了Hyperledger Fabric的chaincode容器生成过程的示意图:Peer通过Docker Client发起对Docker Daemon的调用,以fabric-ccenv为基础镜像创建出一个chaincode构建容器(chaincode builder container)Peer将chaincode源代码传入chaincode构建容器,在里面完成智能合约编译Peer再调用Docker Daemon创建以fabric-baseos为基础镜像的chaincode镜像,并将在第2步编译好的chaincode二进制文件内置在chaincode镜像中。启动运行chaincode容器。从上述过程我们分析一下这里面存在的一些问题:由于该过程是独立于Kubernetes体系之外运行的,难以对chaincode容器进行生命周期管理。无法基于Kubernetes的namaspace隔离、NetworkPolicy等机制实现对chaincode容器的安全管理。针对上面分析发现的问题,我们研究了几种问题解决的思路。第一种思路,是将chaincode容器纳入到Kubernete的体系(如pod)进行管理。这在我们的角度看来,其实是最理想的方案。因为它不仅可以实现chaincode容器全生命周期与Fabric其他类型节点一致的管理方式,并且可以结合Kubernetes的NetowrkPolicy控制chaincode容器的网络访问策略。其实此前Hyperledger Fabric社区已经创建了一个相关的需求即JIRA(FAB-7406),但目前仍未实现。假设未来在此功能实现之后,我们进一步展望一下,还可以将智能合约的容器调度运行于Serverless Kubernetes之上,提供kernal级别的隔离,保证应用容器之间的安全隔离。第二种思路,如社区和网上的一些观点所提到的,将chaincode容器放入Docker-in-Docker(DIND)环境运行。这个思路背后的出发点,主要是为了降低对宿主机Docker Daemon的依赖以及动态生成chaincode镜像和容器的不可管理性。对于这个思路,我们也基于Hyperledger Fabric和Kubernetes进行了试验,在右边的这部分Kubernetes部署模板yaml代码里,绿色框的部分是用于配置DIND的容器作为peer pod的一个sidecar,同时将DIND容器内的Docker Daemon通过本地端口2375以环境变量的形式配置到peer的参数中,使得peer可以将chaincode创建相关请求发送到DIND内。通过对结果的观察和分析,我们发现了以下这几点。DIND的思路有如下一些优点:无需依赖宿主节点的/var/run/docker.sock。无需专门清理每个Kubernetes worker节点的chaincode镜像。但DIND有着一些更为明显的不足:每次创建部署或恢复peer节点会变得很慢,因为DIND内需要去拉取fabric-ccenv镜像,其大小约1.4GB;而如果用传统部署方式的话,只需在worker节点拉取一次镜像即可。Chaincode的实例化(instantiate)过程稍微变慢,推测这和DIND容器本身运行所需的开销有一定关系。当peer节点或者整个组织(organization)删掉重建之后(复用原有的数据目录),启动速度比起传统方式会慢很多,这背后的原因和第1点相同。在业界实践中,DIND方法主要用于CI/CD的场景,但对于生产环境使用的话,则在稳定性等方面仍有较多的挑战。DIND的思路仍然不能解决chaincode容器的安全访问控制和隔离的问题。第三种思路,是我们目前在BaaS中采用的方法,即综合各种配置的手段先解决最主要的问题。这包括以下几个方面的工作:首先,通过Fabric peer的合理配置(如图中右上角的示例配置)保证chaincode和peer的通信。其次,使用docker rm和docker rmi命令清理chaincode容器和镜像(它们均包含“dev-”前缀)。这里面有不同的可选位置。2.1 适合事后清理的可选位置是采用DaemonSet结合lifecycle.preStop.exec.command的位置来运行这些清理命令。2.2 适合事前清理的可选位置是在initContainer中运行上述清理命令。采用iptables规则,对chaincode容器进行网络隔离。主要是通过在Helm Chart安装阶段配置Kubernetes worker节点的iptables规则,实现限制chaincode容器对Kubernetes网络和对外部网络的访问(同时也可以限制进入chaincode容器的网络访问)。通过上述一系列手段,我们得到了对chaincode容器实现生命周期管理、安全隔离和网络访问限制的一个实用的方案。未来我们也会继续朝着思路一这种最理想方式进行更多的探索。今天阿里巴巴集团的区块链已经在多个行业、多种场景实现了结合以及业务落地,包含了如商品溯源、数字内容版权、供应链金融、数据资产共享、公益慈善、医疗处方等等。我们的客户在生产环境已经达到了百万级的交易规模以及百GB的账本数据,这也为我们提供了很丰富的区块链应用实践经验。基于这些实践,我们想跟大家分享的是,其实区块链应用设计开发并不复杂,这一页总结了构建于Kubernete之上的区块链系统和应用的基本模式。可以看到,Kubernetes帮我们解决了底层基础架构和资源的复杂性,提供了应用的标准底座;而区块链服务BaaS则帮我们解决了区块链系统配置部署和运维的复杂性,提供了统一的接口,那么对企业来说,便可以聚焦在业务流程和业务逻辑的实现,及业务应用的开发上,以及与业务数据的交互和管理上来,实现核心价值的最大化。下面,我们将进行阿里云BaaS Hyperledger Fabric的一个demo,主要展示一下几方面的过程:首先,快速创建跨企业(跨账号)、跨region的联盟链。接着,动态添加新组织、新通道,完成企业间协同,包括邀请企业,以及企业各自的审批流程。在一些关键操作点上,BaaS内置了风控保障,强制邀请短信验证才允许完成操作,这看似麻烦的环节实际上是企业对生产安全保障以及审计都非常看重和需要的。最后,我们在BaaS上部署了经典的Marbles虚拟数字资产交易的应用,包含chaincode的部署和client SDK应用的部署。最后,欢迎有兴趣的朋友进一步了解和使用阿里云的区块链服务BaaS,通过扫描图中的两个二维码可快速访问相关产品主页,申请开通免费公测试用,以及访问产品文档获得更多使用和开发指南。以上就是我今天跟大家分享的全部内容,谢谢大家!本文作者:余珊阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 19, 2018 · 1 min · jiezi

docker使用笔记

docker的安装这里不再赘述,直接pip安装即可一、创建私有仓库安装docker1.7之后版本,在仓库主机做如下操作注:仓库的主机是ubuntu,其他系统会稍有不同#修改docker启动项vim /etc/docker/daemon.json#添加内容:{ “insecure-registries”:["${addressOfBasicImage}"] }#修改docker配置:vim /etc/default/docker#在DOCKER_OPTS值中添加以下内容DOCKER_OPTS="–insecure-registry 0.0.0.0/0"#重新加载daemonsystemctl daemon-reload#重启dockersystemctl restart docker#拉取仓库镜像,${addressOfRegistry}代表仓库镜像地址,例如:10.75.9.72:5000docker pull ${addressOfRegistry}/registry#启动容器建立私有仓库镜像,${addressOfRegistry}代表仓库镜像地址,例如:10.75.9.72:5000docker run -d -it -p 5000:5000 –name registry ${addressOfRegistry}/registry bash二.管理镜像:在节点主机进行如下操作#拉取目标镜像docker pull public-docker-virtual.dns/python:3.6#启动容器run -d -v /opt/registry:/var/lib/registry -i –restart=always –name python3 public-docker-virtual.dns/python:3.6#修改容器配置,安装需要打入基础镜像的库#打好标签docker tag public-docker-virtual.dns/python:3.6 10.9.220.139:5000/python3:latest#上传到仓库docker push 10.9.220.139:5000/python3查看仓库的镜像:三.配置docker上网代理本章节适用于宿主机使用代理访问网络的情况,如果宿主机不用使用代理上网,可以直接跳过本节在宿主机上配置dockercentos7:在目录/etc/systemd/system/docker.service.d中新建文件http-proxy.conf,在文件中添加内容:[Service]Environment=“HTTP_PROXY=http://proxy_addr:proxy/” “HTTPS_PROXY=https://proxy_addr:proxy/“然后重启docker服务如果需要在镜像中需要访问外网的权限,只用加环境变量即可:export http_proxy=proxy_addr:proxyexport https_proxy=proxy_addr:proxyexport proxy=proxy_addr:proxy

December 17, 2018 · 1 min · jiezi

开源 serverless 产品原理剖析(二) - Fission

摘要: Fission 是由私有云服务提供商领导开源的 serverless 产品,它借助 kubernetes 灵活强大的编排能力完成容器的管理调度工作,而将重心投入到 FaaS 功能的开发上,其发展目标是成为 AWS lambda 的开源替代品。背景本文是开源 serverless 产品原理剖析系列文章的第二篇,关于 serverless 背景知识的介绍可参考文章开源 serverless 产品原理剖析(一) - Kubeless,这里不再赘述。Fission 简介Fission 是由私有云服务提供商 Platform9 领导开源的 serverless 产品,它借助 kubernetes 灵活强大的编排能力完成容器的管理调度工作,而将重心投入到 FaaS 功能的开发上,其发展目标是成为 AWS lambda 的开源替代品。从 CNCF 视角,fission 属于 serverless 平台型产品。核心概念Fission 包含 Function、Environment 、Trigger 三个核心概念,其关系如下图所示:Function - 代表用特定语言编写的需要被执行的代码片段。Environment- 用于运行用户函数的特定语言环境。Trigger - 用于关联函数和事件源。如果把事件源比作生产者,函数比作执行者,那么触发器就是联系两者的桥梁。关键组件Fission 包含 Controller、Router、Executor 三个关键组件:Controller - 提供了针对 fission 资源的增删改查操作接口,包括 functions、triggers、environments、Kubernetes event watches 等。它是 fission CLI 的主要交互对象。Router - 函数访问入口,同时也实现了 HTTP 触发器。它负责将用户请求以及各种事件源产生的事件转发至目标函数。Executor - fission 包含 PoolManager 和 NewDeploy 两类执行器,它们控制着 fission 函数的生命周期。原理剖析本章节将从以下几个方面介绍 fission 的基本原理:函数执行器 - 它是理解 fission 工作原理的基础。Pod 特化 - 它是理解 fission 如何根据用户源码构建出可执行函数的关键。触发器 - 它是理解 fission 函数各种触发原理的入口。自动伸缩 - 它是理解 fission 如何根据负载动态调整函数个数的捷径。日志处理 - 它是理解 fission 如何处理各函数日志的有效手段。本文所做的调研基于kubeless 0.12.0和k8s 1.13。函数执行器CNCF 对函数生命周期的定义如下图所示,它描绘了函数构建、部署、运行的一般流程。要理解 fission,首先需要了解它是如何管理函数生命周期的。Fission 的函数执行器是其控制函数生命周期的关键组件。Fission 包含 PoolManager 和 NewDeploy 两类执行器,下面分别对两者进行介绍。PoolManagerPoolmgr 使用了池化技术,它通过为每个 environment 维持了一定数量的通用 pod 并在函数被触发时将 pod 特化,大大降低了函数的冷启动的时间。同时,poolmgr 会自动清理一段时间内未被访问的函数,减少闲置成本。该执行器的原理如下图所示。此时,函数的生命周期如下:使用 fission CLI 向 controller 发送请求,创建函数运行时需要的特定语言环境。例如,以下命令将创建一个 python 运行环境。fission environment create –name python –image fission/python-envPoolmgr 定期同步 environment 资源列表,参考 eagerPoolCreator。Poolmgr 遍历 environment 列表,使用 deployment 为每个 environment 创建一个通用 pod 池,参考 MakeGenericPool。使用 fission CLI 向 controller 发送创建函数的请求。此时,controller 只是将函数源码等信息持久化存储,并未真正构建好可执行函数。例如,以下命令将创建一个名为 hello 的函数,该函数选用已经创建好的 python 运行环境,源码来自 hello.py,执行器为 poolmgr。fission function create –name hello –env python –code hello.py –executortype poolmgrRouter 接收到触发函数执行的请求,加载目标函数相关信息。Router 向 executor 发送请求获取函数访问入口,参考 GetServiceForFunction。Poolmgr 从函数指定环境对应的通用 pod 池里随机选择一个 pod 作为函数执行的载体,这里通过更改 pod 的标签让其从 deployment 中“独立”出来,参考 _choosePod。K8s 发现 deployment 所管理 pod 的实际副本数少于目标副本数后会对 pod 进行补充,这样便实现了保持通用 pod 池中的 pod 个数的目的。特化处理被挑选出来的 pod,参考 specializePod。为特化后的 pod 创建 ClusterIP 类型的 service,参考 createSvc。将函数的 service 信息返回给 router,router 会将 serviceUrl 缓存以避免频繁向 executor 发送请求。Router 使用返回的 serviceUrl 访问函数。请求最终被路由至运行函数的 pod。如果该函数一段时间内未被访问会被自动清理,包括该函数的 pod 和 service,参考 idleObjectReaper。NewDeployPoolmgr 很好地平衡了函数的冷启动时间和闲置成本,但无法让函数根据度量指标自动伸缩。NewDeploy 执行器实现了函数 pod 的自动伸缩和负载均衡,该执行器的原理如下图所示。此时,函数的生命周期如下:使用 fission CLI 向 controller 发送请求,创建函数运行时需要的特定语言环境。使用 fission CLI 向 controller 发送创建函数的请求。例如,以下命令将创建一个名为 hello 的函数,该函数选用已经创建好的 python 运行环境,源码来自 hello.py,执行器为 newdeploy,目标副本数在 1 到 3 之间,目标 cpu 使用率是 50%。fission fn create –name hello –env python –code hello.py –executortype newdeploy –minscale 1 –maxscale 3 –targetcpu 50Newdeploy 会注册一个 funcController 持续监听针对 function 的 ADD、UPDATE、DELETE 事件,参考 initFuncController。Newdeploy 监听到了函数的 ADD 事件后,会根据 minscale 的取值判断是否立即为该函数创建相关资源。minscale > 0,则立即为该函数创建 service、deployment、HPA(deployment 管理的 pod 会特化)。minscale <= 0,延迟到函数被真正触发时创建。Router 接收到触发函数执行的请求,加载目标函数相关信息。Router 向 newdeploy 发送请求获取函数访问入口。如果函数所需资源已被创建,则直接返回访问入口。否则,创建好相关资源后再返回。Router 使用返回的 serviceUrl 访问函数。如果该函数一段时间内未被访问,函数的目标副本数会被调整成 minScale,但不会删除 service、deployment、HPA 等资源,参考 idleObjectReaper。执行器比较实际使用过程中,用户需要从延迟和闲置成本两个角度考虑选择何种类型的执行器。不同执行器的特点如下表所示。执行器类型最小副本数延迟闲置成本Newdeploy0高非常低 - pods 一段时间未被访问会被自动清理掉。Newdeploy>0低中等 - 每个函数始终会有一定数量的 pod 在运行。Poolmgr0低低 - 通用池中的 pod 会一直运行。小结Fission 将函数执行器的概念暴露给了用户,增加了产品的使用成本。实际上可以将 poolmgr 和 newdeploy 技术相结合,通过创建 deployment 将特化后的 pod 管理起来,这样可以很自然地利用 HPA 来实现对函数的自动伸缩。Pod 特化在介绍函数执行器时多次提到了 pod 特化,它是 fission 将环境容器变成函数容器的奥秘。Pod 特化的本质是通过向容器发送特化请求让其加载用户函数,其原理如下图所示。一个函数 pod 由下面两种容器组成:Fetcher - 下载用户函数并将其放置在共享 volume 里。不同语言环境使用了相同的 fetcher 镜像,fetcher 的工作原理可参考代码 fetcher.go。Env - 用户函数运行的载体。当它成功加载共享 volume 里的用户函数后,便可接收用户请求。具体步骤如下:容器 fetcher 接收到拉取用户函数的请求。Fetcher 从 K8s CRD 或 storagesvc 处获取用户函数。Fetcher 将函数文件放置在共享的 volume 里,如果文件被压缩还会负责解压。容器 env 接收到加载用户函数的命令。Env 从共享 volume 中加载 fetcher 为其准备好的用户函数。特化流程结束,容器 env 开始处理用户请求。触发器前面的章节介绍了 fission 函数的构建、加载和执行的逻辑,本章节主要介绍如何基于各种事件源触发 fission 函数的执行。CNCF 将函数的触发方式分成了如下图所示的几种类别,关于它们的详细介绍可参考链接 Function Invocation Types。对于 fission 函数,最简单的触发方式是使用 fission CLI,另外还支持通过各种触发器。下表展示了 fission 函数目前支持的触发方式以及它们所属的类别。触发方式类别fission CLISynchronous Req/RepHTTP TriggerSynchronous Req/RepTime TriggerJob (Master/Worker)Message Queue Trigger1. nats-streaming2. azure-storage-queue3. kafka | Async Message Queue || Kubernetes Watch Trigger | Async Message Queue |下图展示了 fission 函数部分触发方式的原理:HTTP trigger所有发往 fission 函数的请求都会由 router 转发,fission 通过为 router 创建 NodePort 或 LoadBalancer类型的 service 让其能够被外界访问。除了直接访问 router,还可以利用 K8s ingress 机制实现 http trigger。以下命令将为函数 hello 创建一个 http trigger,并指定访问路径为/echo。fission httptrigger create –url /echo –method GET –function hello –createingress –host example.com该命令会创建如下 ingress 对象,可以参考 createIngress 深入了解 ingress 的创建逻辑。apiVersion: extensions/v1beta1kind: Ingressmetadata: # 该 Ingress 的名称 name: xxx …spec: rules: - host: example.com http: paths: - backend: # 指向 router service serviceName: router servicePort: 80 # 访问路径 path: /echoIngress 只是用于描述路由规则,要让规则生效、实现请求转发,集群中需要有一个正在运行的 ingress controller。想要深入了解 ingress 原理可参考系列文章第一篇中的 HTTP trigger 章节。Time trigger如果希望定期触发函数执行,需要为函数创建 time trigger。Fission 使用 deployment 部署了组件 timer,该组件负责管理用户创建的 timer trigger。Timer 每隔一段时间会同步一次 time trigger 列表,并通过 golang 中被广泛使用的 cron 库 robfig/cron 定期触发和各 timer trigger 相关联函数的执行。以下命令将为函数 hello 创建一个名为halfhourly的 time trigger,该触发器每半小时会触发函数 hello 执行一次。这里使用了标准的 cron 语法定义执行计划。fission tt create –name halfhourly –function hello –cron “*/30 * * * *“trigger ‘halfhourly’ createdMessage queue trigger为了支持异步触发,fission 允许用户创建消息队列触发器。目前可供选择的消息队列有 nats-streaming、azure-storage-queue、kafka,下面以 kafka 为例描述消息队列触发器的使用方法和实现原理。以下命令将为函数 hello 创建一个基于 kafka 的消息队列触发器hellomsg。该触发器订阅了主题 input 中的消息,每当有消息到达它便会触发函数执行。如果函数执行成功,会将结果写入主题 output 中,否则将结果写入主题 error 中。fission mqt create –name hellomsg –function hello –mqtype kafka –topic input –resptopic output –errortopic error Fission 使用 deployment 部署了组件 mqtrigger-kafka,该组件负责管理用户创建的 kafka trigger。它每隔一段时间会同步一次 kafka trigger 列表,并为每个 trigger 创建 1 个用于执行触发逻辑的 go routine,触发逻辑如下:消费 topic 字段指定主题中的消息;通过向 router 发送请求触发函数执行并等待函数返回;如果函数执行成功,将返回结果写入 resptopic 字段指定的主题中,并确认消息已被处理;否则,将结果写入 errortopic 字段指定的主题中。小结Fission 提供了一些常用触发器,但缺少对 CNCF 规范里提到的Message/Record Streams触发方式的支持,该方式要求消息被顺序处理;如果有其它事件源想要接入可以参考 fission 触发器的设计模式自行实现。自动伸缩K8s 通过 Horizontal Pod Autoscaler 实现 pod 的自动水平伸缩。对于 fission,只有通过 newdeploy 方式创建的函数才能利用 HPA 实现自动伸缩。以下命令将创建一个名为 hello 的函数,运行该函数的 pod 会关联一个 HPA,该 HPA 会将 pod 数量控制在 1 到 6 之间,并通过增加或减少 pod 个数使得所有 pod 的平均 cpu 使用率维持在 50%。fission fn create –name hello –env python –code hello.py –executortype newdeploy –minmemory 64 –maxmemory 128 –minscale 1 –maxscale 6 –targetcpu 50Fission 使用的是autoscaling/v1版本的 HPA API,该命令将要创建的 HPA 如下:apiVersion: autoscaling/v1kind: HorizontalPodAutoscalermetadata: labels: executorInstanceId: xxx executorType: newdeploy functionName: hello … # 该 HPA 名称 name: hello-${executorInstanceId} # 该 HPA 所在命名空间 namespace: fission-function …spec: # 允许的最大副本数 maxReplicas: 6 # 允许的最小副本数 minReplicas: 1 # 该 HPA 关联的目标 scaleTargetRef: apiVersion: extensions/v1beta1 kind: Deployment name: hello-${executorInstanceId} # 目标 CPU 使用率 targetCPUUtilizationPercentage: 50想了解 HPA 的原理可参考系列文章第一篇中的自动伸缩章节,那里详细介绍了 K8s 如何获取和使用度量数据以及目前采用的自动伸缩策略。小结和 kubeless 类似,fission 避免了将创建 HPA 的复杂细节直接暴露给用户,但这是以牺牲功能为代价的;Fission 目前提供的自动伸缩功能过于局限,只对通过 newdeploy 方式创建的函数有效,且只支持基于 cpu 使用率这一种度量指标(kubeless 支出基于 cpu 和 qps)。本质上是因为 fission 目前仍然使用的是 v1 版本的 HPA,如果用户希望基于新的度量指标或者综合多项度量指标可以直接使用 hpa-v2 提供的功能;目前 HPA 的扩容缩容策略是基于既成事实被动地调整目标副本数,还无法根据历史规律预测性地进行扩容缩容。日志处理为了能更好地洞察函数的运行情况,往往需要对函数产生的日志进行采集、处理和分析。Fission 日志处理的原理如下图所示。日志处理流程如下:使用 DaemonSet 在集群中的每个工作节点上部署一个 fluentd 实例用于采集当前机器上的容器日志,参考 logger。这里,fluentd 容器将包含容器日志的宿主机目录/var/log/和/var/lib/docker/containers挂载进来,方便直接采集。Fluentd 将采集到的日志存储至 influxdb 中。用户使用 fission CLI 查看函数日志。例如,使用命令fission function logs –name hello可以查看到函数 hello 产生的日志。小结目前,fission 只做到了函数日志的集中化存储,能够提供的查询分析功能非常有限。另外,influxdb 更适合存储监控指标类数据,无法满足日志处理与分析的多样性需求。函数是运行在容器里的,因此函数日志处理本质上可归结为容器日志处理。针对容器日志,阿里云日志服务团队提供了成熟完备的解决方案,欲知详情可参考文章面向容器日志的技术实践。总结在介绍完 fission 的基本原理后,不妨从以下几个方面将其和第一篇介绍的 kubeless 作一个简单对比。触发方式 - 两款产品都支持常用的触发方式,但 kubeless 相比 fission 支持的更全面,且更方便接入新的数据源。自动伸缩 - 两款产品的自动伸缩能力都还比较基础,支持的度量指标都比较少,且底层都依赖于 K8s HPA。函数冷启动时间 - fission 通过池化技术降低了函数冷启动时间,kubeless 在这一块并未作过多优化。高级功能 - fission 支持灰度发布、自定义工作流等高级功能,kubeless 目前还不支持。参考资料Fission ArchitectureHow to Develop a Serverless Application with FissionFUNCTION EXECUTORS本文作者:吴波bruce_wu阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 11, 2018 · 4 min · jiezi

阿里数据库的极致弹性之路

阿里妹导读:数据库从IOE(IBM小机、Oracle商业DB、EMC存储)一路走来,大家都知道数据库是资源重依赖的软件,对服务器的三大件CPU、内存、磁盘几乎都有要求。数据库作为广泛使用的数据存储系统,其SQL请求背后涉及的物理读、逻辑读、排序过滤等消耗了IO和CPU资源,业务SQL不同,执行计划不同,资源消耗就不同,因而不同业务对资源规格的需求也不一样。正因如此,我们更需要抽象规格,更好地让不同资源诉求的数据库实例混跑在相同的物理机上,提升整体利用率。今天,阿里资深技术专家天羽为我们讲述阿里数据库的极致弹性之路。除了日常业务需求,阿里的双11场景,让我们持续思考如何低成本高效率地支持峰值流量,把这些思考变成现实,变成技术竞争力。在大促资源弹性上有这么几个思路:使用公共云标准资源弹性,直接用阿里云的标准资源支撑大促后归还。这个是最直接的想法,但这里的难度是业务需求和云资源在性能、成本上的差距,不要定制化机器。混部能力,存量业务的分类混部、分时混部。使用离线资源支撑大促,既是分类混部,双11零点离线降级,高峰后在线归还资源也是分时复用。快上快下,在有能力使用云、离线资源后,尽量缩短占用周期。碎片化资源,数据库一直是块石头,是一个大块完整的规格。如果把数据库自己的大库变成小库,就可以使用其他业务的碎片化资源,包括公共云上的资源。大促的成本=持有资源X持有周期,更通用的资源(云)、更快的部署(容器化)是缩短持有周期的关键,如何更少地使用资源(使用离线或只扩计算资源),就依赖存储计算分离架构的实施。沿着极致弹性的目标,数据库经历了混合云弹性、容器化弹性、计算存储分离弹性三个阶段,基础架构从高性能ECS混合云、容器化混合云、存储计算分离的公共云和离线混部一步步升级。基本上架构演进就是每年验证一个单元,第二年全网铺开,每年挖个坑然后和团队一起努力爬出来,每次演进需要跨团队背靠背紧密合作,快速拿下目标,这也是阿里最神奇的力量。借助于底层软硬件技术发展,一步步的架构升级使得弹性混部越来越灵活和快速。 一、混合云弹性,高性能ECS应运而生2015年之前,我们的大促弹性叫人肉弹性,也就是大促要搬机器,比如集团用云的机型支撑大促,大促结束后搬机器归还给云。但就在2015年底的一次会议上,李津问能否把数据库跑到ECS上,如果可以,就真正帮助了云产品成熟,当时张瑞和我讨论了一下,在会议上就答复了:我们决定试一下。这个合作非常契合会议主题“挑战不可能——集团技术云计算战区12月月会召集令”。对于数据库跑在虚拟机上,我们判断最大的消耗在IO和网络的虚拟化上,因此如何做到接近本机性能,怎么穿透虚拟化就是一个问题。网络的用户态技术DPDK已经比较成熟,但如何做到足够高的效率,是否offload到硬件来做计算是个问题。文件系统IO的用户态链路有个Intel的SPDK方案,Intel推出后各大厂商还在验证中,还没有规模的应用。我们就在这个时候启动的这个项目,叫高性能ECS。通过和ECS团队紧密合作,最终我们做到了最差场景高性能ECS相比本地盘性能损耗低于10%。2016年在集团通过了日常验证,2017年大促开始大规模用云资源直接弹性。这个项目除了打造高性能ECS产品,更重要的是沉淀了网络和文件IO的纯用户态链路技术,这是一个技术拐点的产生,为阿里后续存储计算分离相关产品的高性能突破打下了基础。二、容器化弹性,提升资源效率随着单机服务器的能力提升,阿里数据库在2011年就开始使用单机多实例的方案,通过Cgroup和文件系统目录、端口的部署隔离,支持单机多实例,把单机资源利用起来。但依然存在如下问题:内存的OOM时有发生存在IO争抢问题多租户混部存在主机账号等安全问题数据库主备机型一致性随着单机部署密度越来越高,社区Docker也开始发展起来,尽管还不成熟,Docker本身依赖Cgroup做资源隔离,解决不了Cgroup的IO争抢或OOM问题,但它通过资源隔离和namespace隔离的结合,尝试对资源规格以及部署做新的定义,因此我们看到了容器化更多的优势:标准化规格,数据库与机型解耦,主备不需要对称。这对规模化运维带来极大的效率。Namespace隔离带来混部能力,资源池统一。不同数据库类型,不同数据库版本随便混。让DB具备与其他应用类型混部的条件。2015年数据库开始验证容器化技术,2016年在日常环境中大量使用。因此在集团统一调度的项目启动后,我们就定下了2016年电商一个交易单元全部容器化支撑大促的目标,承载交易大盘约30%,并顺利完成。2017年数据库就是全网容器化的目标,目前数据库全网容器化比例已经接近100%。容器化除了提升部署弹性效率,更重要的是透明底层资源差异,在没有启动智能调度(通过自动迁移提升利用率)前,仅仅从容器化带来的机器复用和多版本混部,就提升了10个点的利用率,资源池的统一和标准部署模板也加快了资源交付效率。容器化完成了底层各种资源的抽象,标准化了规格,而镜像部署带来了部署上的便利,基于数据库PaaS和统一调度层的通力合作,数据库的弹性变得更加快速灵活,哪里有资源,哪里就能跑起数据库。 三、计算资源极致弹性,存储计算分离架构升级实现了容器化混合云,是不是每年大促使用高性能ECS,容器化部署就可以了呢?其实还是有不足的:数据库弹性需要搬数据,把数据搬到ECS上是非常耗时的工作。 弹性规模太大,如果超过公有云售卖周期,会增加持有成本。因此如何做到更快、更通用的弹性能力,是一个新的技术问题。随着2016年调度的发展,大家考虑机器是不是应该无盘化,是不是应该存储计算分离,从而加快调度效率,而数据库的存储计算分离更是争议很大。数据库的Share Nothing分布式扩展已经深入人心,存储计算分离会不会回到IOE状态?如果IDC是一个数据中心,应用就是计算,DB就是存储,DB自己再做存储计算分离有意义吗?数据是主备双副本的,存储计算分离后变成三副本,存储集群的容量池化能balance掉额外副本的成本吗?为此我开始测算存储计算分离架构在大促场景下的投入产出,我们来看下大促场景,弹性大促时,业务需求计算能力数倍甚至10倍以上扩容,承担大促峰值压力,而磁盘因为存储长期数据,峰值的数据量在整体占比不高,因此磁盘容量基本不需要扩容。在以前本地磁盘跑主备的架构,无法计算、存储分开扩容,大促指标越高,添加标准机器越多,成本浪费越大,因为磁盘是标准数据库机器的主要成本。而存储计算分离的情况下,测算下来,我们看到在较低日常压力下存储计算分离成本是比本地盘高的,但再往上,存储计算分离只需要增加计算,存储集群因为池化后,不只容量池化了,性能也池化了,任何高负载实例的IO都是打散到整个集群分担的,磁盘吞吐和IOPS复用,不需扩性能,成本优势非常明显。磁盘不扩容,只扩计算自然成本低很多。传统的思考是存储集群容量池化的优势,但在大促场景我们更多用到的是性能的池化,突破单机瓶颈,因此我们提出了电商异地多活所有单元存储计算分离,其余业务继续使用本地磁盘进行同城容灾的目标架构。提出这个设想,而这个架构的可行性如何判断?基于一些数字就可以推断,大家知道SSD磁盘的读写响应时间在100-200微秒,而16k的网络传输在10微秒内,因此尽管存储计算分离增加两到三次的网络交互,加上存储软件本身的消耗,整体有机会做到读写延时在 500微秒的范围内。在数据库实例压测中我们发现,随着并发增加,存储集群具备更大的QPS水位上线,这印证了性能池化突破单机瓶颈带来的吞吐提升。数据库团队在2017年开始验证存储计算分离,基于25G的TCP网络实现存储计算分离部署,当年就承担了10%大促流量。我们基于分布式存储做到了700微秒的响应时间,这里内核态和软件栈的消耗较大,为此X-DB也针对性地做了慢IO优化,特别是日志刷盘的优化,开启原子写去掉了double write buffer提升吞吐能力。这个过程中,我们沉淀了存储的资源调度系统,目前已经作为统一调度的组件服务集团业务。我们对当前架构性能不太满意,有了X-DB的慢IO优化、存储计算分离跨网络的IO路径、存储资源调度等技术沉淀,加上阿里巴巴RDMA网络架构的发展,2017下半年数据库开始和盘古团队一起,做端到端全用户态的存储计算分离方案。四、全用户态IO链路的存储计算分离架构落地 从数据库软件X-DB的IO调用开始,就走我们自己研发的用户态文件系统DBFS,DBFS使用盘古的用户态客户端,直接通过RDMA网络访问后端盘古分布式文件系统,整个IO链路完全绕过了内核栈。这里DBFS绕过了内核文件系统,自然也绕过了pagecache,为此DBFS针对数据库场景,实现了更简洁高效的BufferIO机制。因为IO都是跨网络远程访问,因此RDMA起到了重要作用,以下是RDMA与TCP网络在不同包大小下的延时对比,除了延时优势外,RDMA对长尾IO的tail latency能够有效控制,对一个数据库请求涉及多次IO来说,对用户请求的响应时间能够更有效保证。RDMA技术的应用是DB大规模存储计算分离的前提条件,通过我们的数据实测,DBFS+RDMA链路的延时已经和Ext4+本地盘达到相同水平。今年我们首次大规模部署RDMA,如履薄冰。经过多次压测、演练, RDMA配套监控和运维体系建设已经完善起来,我们能够在1分钟内识别服务器网卡或交换机的网络端口故障触发告警,能够故障快速隔离,支持业务流量快速切走,支持集群或单机的网络RDMA向TCP降级切换等等。在我们的切流演练中,从DBFS看到RDMA链路的写延时比TCP降低了一倍。我们在全链路压测中,基于RDMA技术保障了在单个数据库实例接近2GB吞吐下磁盘响应时间稳定在500微秒左右,没有毛刺。盘古分布式存储为了同时支持RDMA、EC压缩、快照等功能,做了大量的设计优化,尤其对写IO做了大量优化,当然也包括RDMA/TCP切流,故障隔离等稳定性方面的工作。作为阿里的存储底盘,其在线服务规模已经非常庞大。整个技术链路讲清楚之后,说一下我们在规模应用中遇到的难题,首先,容器的网络虚拟化Bridge和RDMA天然不兼容,由于容器走Bridge网络模式分配IP,而这个是走内核的。为了应用RDMA,我们必须使用Host网络模式进行容器化,走Host + X-DB + DBFS + RDMA +盘古存储这样的全用户态链路。其次,对于公有云环境,我们通过VPC打通形成混合云环境,因此应用通过VPC访问数据库,而数据库使用物理IP用于RDMA访问盘古以及X-DB内部X-Paxos。这个方案复杂而有效,得益于DBPaaS管控的快速迭代和容器化资源调度的灵活性,这些新技术能够快速落地,在变化中稳步推进。今年年初,我们定下了2018大促的支撑形态,即异地多活的中心机房将计算弹性到大数据的离线资源,单元机房将计算弹性到公共云资源,不搬数据直接弹性扩容,快上快下的大促目标。今年DB全局一盘棋,完成了资源调整,实现了电商各站点的存储计算分离架构升级,并通过X-DB异地多副本架构灵活部署,实现了弹性大促目标。基于底层盘古分布式的共享存储,弹性不需要迁移数据,只需要挂载磁盘,数据库可以像应用一样快速弹性,做到一个集群10分钟完成弹性扩容。同时在全链路压测过程中,对出现性能瓶颈的业务,我们可以边压边弹,快速弹到更大的规格上。基于快速弹性的能力,今年DB所有站点的大促扩容都在三天内完成,这在以前是不可能实现的,这就是存计分离的架构带来的效率。最后,感谢阿里内部通力合作的盘古、网络、调度、IDC等团队,正是大家的支持让阿里数据库的基础架构才能不断升级,不断提升效率和成本的竞争力。数据库存储计算分离的架构升级,大大节约了大促资源成本。目前我们的弹性能力正在日常化,通过数据预测,自动触发弹性扩容,我们的目标是让单机容量问题导致故障成为历史。 接下来我们平台将向智能化发展,对于数据库来说,只有基础架构足够强大,足够快速,灵活,弹性,智能化才能有效发挥。本文作者:天羽 阅读原文本文来自云栖社区合作伙伴“阿里技术”,如需转载请联系原作者。

December 11, 2018 · 1 min · jiezi

完爆 Best Fit,看阿里如何优化 Sigma 在线调度策略节约亿级成本

摘要:2018 年“双 11”的交易额又达到了一个历史新高度 2135 亿。相比十年前,我们的交易额增长了 360 多倍,而交易峰值增长了 1200 多倍。相对应的,系统数呈现爆发式增长。系统在支撑“双 11”过程中的复杂度和难度呈现指数级形式上升趋势。作为阿里巴巴全集团范围的容器调度系统,Sigma 在“双11”期间成功支撑了全集团所有容器(交易线中间件、数据库、广告等 20 多个业务)的调配,是阿⾥巴巴运维系统重要的底层基础设施。Sigma 已经是阿里全网所有机房在线服务管控的核心角色,管控的宿主机资源达到百万级,重要程度不言而喻,其算法的优劣程度影响了集团整体的业务稳定性,资源利用率。当用戶向调度系统申请容器所需的计算资源(如 CPU 、 内存、磁盘)时,调度器负责挑选出满足各项规格要求的物理机来部署这些容器。在相同的资源需求下,调度策略的优劣决定着集群计算资源利用的水平。本文将简要介绍群体增强学习算法在调度策略优化中的应用。1.计算资源调度及在线策略当用户向 Sigma 申请容器所需的计算资源(如 CPU、Memory、磁盘等)时,调度器负责挑选出满足各项规格要求的物理机来部署这些容器。通常,满足各项要求的物理机并非唯一,且水位各不相同,不同的分配方式最终得到的分配率存在差异,因此,调度器的一项核心任务就是按照某一策略从众多候选机器中挑出最合适的物理机。在文献中,计算资源调度一般被表述为矢量装箱问题(vector bin packing problem),如果各应用的容器数量事先已知(如大促场景),调度器可一次性为所有容器生成优化的排布方案,此时问题可以表述为整数规划,可使用通用求解器或专门开发的算法来求解;如果各应用的请求陆续到达 Sigma (如日常场景),调度器需要在每次请求到达时即时(在线)生成部署决策,此时问题可表述为马尔可夫决策过程 (Markov Decision Process, MDP),原则上可以通过值迭代或策略迭代求得最优策略。最常用的调度策略包括 First-Fit (FF) 和 Best-Fit (BF)。如果使用 First-Fit算法,调度器会将容器部署到遍历中碰到的第一个满足所有要求的物理机上;而Best-Fit算法则会在满足要求的物理机中挑选分配水位最高的机器来部署容器。对于经典的 bin packing 问题(即一维矢量装箱问题),First-Fit 和 Best-Fit 的近似比均为1.7,即二者都可保证所使用的机器数不超出最优方案的170%;对于2维及以上的矢量装箱问题,理论上不存在有着明确近似比保证的多项式算法。当物理机的某个资源维度明显为瓶颈而导致其它资源维度普遍有剩余时,其有效维度可视为1,使用 First-Fit 或 Best-Fit 一般可以取得不错的分配率;而一旦瓶颈并未集中体现在同一维度,两种策略的效果就要大打问号了。除了资源维度上的要求,实际调度中还有容灾和干扰隔离上的考虑:比如同一应用的容器不允许全部部署到同一台物理机上,很多应用甚至每台机器上只允许有一个实例;某些应用之间还存在互斥关系(如资源争抢),严重影响应用的性能,因此也不允许它们被部署到同一物理机上。这些限制条件的引入,使得常用策略越发水土不服了。通过人肉反复试错,勉强扛住了多次大促建站的压力。然而,随着各业务的扩张,线上容器的规模越来越大,资源变得越来越紧张,人肉调参的效率渐渐力不从心。为了把调度同学从调参中解放出来,让有限的资源扛住更大的压力,达摩院机器智能技术实验室(M.I.T.)的决策智能算法团队和Sigma调度团队展开了紧密合作,对在线调度策略问题进行了研究,并开发了基于群体增强学习(SwarmRL)的算法。2.在线调度模型记当前待部署容器的规格为向量 p∈P,为其分配资源时集群状态为向量 s∈S , 候选物理机的集合为 A⊆A,策略可表示为函数 :S×P→A(∈)。当按策略 选择物理机 a=(s,p)来部署该容器时,该选择的即时成本为 r(a),集群的新状态 s′ 由状态量 s 、p 以及动作 a 共同决定,记为 s′=L(s,p,a) ;记后续到达的容器规格 p′, 对于在线调度,p′ 为随机量。引入折扣系数 ∈[0,1],系统的 Bellman 方程为:最优调度策略可表示为:理论上,通过随机梯度下降,我们可以在策略空间 中搜索较优的策略,但相要更进一步的优化,甚至得到全局最优策略,则需要借助其它方法,特别是当最优策略可能是 multi-modal 形式。3.群体增强学习 SwarmRL为防止策略的优化陷入较差的局部最优解,同时拥有较快的收敛速度,我们基于群体增加学习的框架来设计算法。与传统的增强学习方法相比,算法使用多个 agent 来探索问题的策略空间,且多个 agent 之间存在互相学习机制,这使得算法有了跳出局部陷阱的能力。为获取各状态值(V^^)的估计,一个准确的 Sigma 模拟器必不可少,团队内部同学基于 Sigma 的调度器开发了“完全保真”的模拟器 Cerebro 。算法首先随机初始化一群 agent 的策略,针对每个策略,通过模拟器获取相应的的状态值估计,记录当前全局最佳策略。在后续的每次迭代中,各个 agent 不断更新自身的局部最佳策略,并参照局部最佳策略与群体当前全局最佳策略,对 agent 自身的当前策略进行更新,再进行模拟,获取新策略的状态值估计,更新全局最佳策略。如此循环,直到满足收敛条件。在各个 agent 状态值的估计中,样本(多个随机抽取的集群快照和扩容请求序列)和各 agent 的当前策略被输入模拟器 Cerebro,追踪模拟时集群状态的轨迹,即可得到该轨迹的总成本;基于多个样本的轨迹总成本求平均,即得到相应策略下的状态估计值。在 SwarmRL 中,策略的演进方向与步长用“速度” (v) 来表示,速度的变化涉及局部最佳策略 (L) 和群体全局最佳策略 (G ) 与 agent 当前策略 () 的差异,并受策略惯性因子 w、本地学习因子C1(self-learning)、群体学习因子 C2 (social-learning) 等参数的调控:其中 1,2∈[0,1] 为随机量,为可行性保持映射,用于将逸出可行域的 agent 重新“拉回”可行域。在迭代中,局部最佳策略 (L) 和群体全局最佳策略 (G ) 不断更新:4.算法应用下面我们先用一个随机生成的小算例来对比一下算法的效果。算例中涉及 30 个应用(见下表),其容器规格主要为 4c8g 与 8c16g,所用宿主机的规格均为 96c512g。若在调度时,请求的顺序和数量均为已知(“上帝视角”),即进行事后排布,使用整数规划求得的最优解对应的分配率为 94.44 % (这也是所有调度策略在该算例上所得分配率的上界),共启用 15 台宿主机,具体排布方案为:现实场景中,每个请求所处顺序和容器数量仅在其到达 Sigma 时才揭晓,若采用 Best-Fit 进行动态调度,所得分配率为 70.83%,共启用 20 台宿主机,具体排布如下:若采用 SwarmRL 学习所得策略进行动态分配,分配率为 94.44%,共启用 15 台宿主机,最终容器排布如下:在该算例中,SwarmRL 学习所得策略的表现(94.44%)与“上帝视角”下最优排布的表现(上界)一致,明显优于 Best-Fit 的表现(70.83%),改进幅度达 23.61%.我们再随机生成规模较大的请求数据:共计 3K 个请求,5K 个容器,其规格分布如下图,由于该场景下整数规划模型的变量规模太大,已经无法在短时间内直接求取“上帝视角”的最优解。对比 Best-Fit (以及人肉策略),算法所得新策略的效果如下:相对于 Best-Fit,新策略节约宿主机 13 台(4.48%),分配率提升 4.30%;相对于人肉策略,新策略节约 7 台(2.46%)宿主机,分配率改进 2.36%.考虑到实际场景中应用请求到达顺序的随机性,我们随机打乱请求生成多个不同的请求顺序,再分别应用三个策略按不同的请求顺序进行动态分配:Best-Fit 在不同请求顺序下宿主机数量的极差为 39 台,相对人肉策略的 84 台而言,表现相对稳定,其波动幅度约为人肉策略的一半;人肉策略的平均分配率低至 81.85%,对比原顺序下的 93.44%,可见人肉策略的性能并不稳定,表现出较剧烈的波动。而学习所得新策略的表现则相当稳定,其宿主机数量的极差仅为 3 台,波动幅度约为人肉策略的 30 分之一;新策略的分配率平均比人肉策略的分配率高 13.78%,比 Best-Fit 的高 3.02%.5.总结与展望从提升分配率、节省资源的角度来看,SwarmRL 算法可以产生出优于常用(以及人肉)的策略,并且有着较为稳定的表现。算法部署到线上环境后,公共资源池的分配率峰值与之前相比有了明显的提升。随着 CPU share 和混部的铺开,除分配率外,新的场景将涉及更多目标,比如打散、负载均衡等,这些目标甚至还有互相矛盾的地方,而 SwarmRL 的运行机制天然适合具有多个目标的策略优化问题,可以十分方便地在策略空间中构造 Pareto Front,因而,后续我们将继续研究新场景下的在线调度策略问题,充分挖掘 SwarmRL 的潜力,进一步提升 Sigma 的调度能力。参考文献David Simchi-Levi, Xin Chen and Julien Bramel (2014). The Logic of Logistics: Theory, Algorithms, and Applications for Logistics Management (3rd ed). SpringerRichard S. Sutton and Andrew G. Barto (2017). Reinforcement Learning: An Introduction. The MIT PressHitoshi Iima, Yasuaki Kuroe and Kazuo Emoto (2011). Swarm reinforcement learning methods for problems with continuous state-action space, IEEE ICSMCYossi Azar, Ilan R. Cohen, Amos Fiat and Alan Roytman (2016). Packing small vectors. SODA'16Yossi Azar, Ilan R. Cohen, Seny Kamara and Bruce Shepherd (2013). Tight bounds for online vector bin packing. STOC‘13本文作者:amber涂南阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 7, 2018 · 2 min · jiezi

重磅!阿里巴巴工程师获得 containerd 社区席位,与社区共建云时代容器标准

重磅!阿里巴巴工程师获得 containerd 社区席位,与社区共建云时代容器标准11 月 29 日,CNCF containerd 社区正式宣布:两位阿里巴巴工程师正式获得 containerd 社区席位,成为 containerd 社区 Reviewer,未来将共同参与云时代容器标准的建设。containerd 是一个工业级别的容器运行时管理引擎,代表的是 Open Containers Initiative(OCI) 标准的最佳实践。同时,containerd 也是 CNCF(云原生计算基金会)的孵化项目,正逐渐发展成 Kubernetes 生态中容器引擎首选。值得一提的是,在阿里巴巴开源容器技术 PouchContainer 的体系架构中,containerd 同样作为基础运行时的重要管理组件存在,而 PouchContainer 更加专注于上层容器功能的开发,例如 Container Runtime Interface(CRI) 接口的实现与增强,容器隔离性的增强等。Reviewer 是 containerd 社区中的核心重要角色。具体而言,reviewer 需要履行项目维护义务,比如 issue 的管理、社区提交的代码审核等。其中,社区代码合并之前,在代码审核时,reviewer 手中有着关键的一票(两票可以确保代码合入)。阿里巴巴自 8 年前的 T4 时代即开始研究容器技术,目前集团在线业务通过 PouchContainer 技术已经实现 100%容器化。PouchContainer 容器技术,已然成为 双11 大促场景下阿里巴巴的核心基础设施技术之一,为 双11 超级工程提供精细化的资源管控与优化方案。正如上文所说,containerd 作为容器技术实现的关键路径,同样举足轻重。服务好阿里巴巴集团之外,PouchContainer 的开源以及 containerd 社区的投入,可以说是阿里巴巴集团实施中台战略过程中,必须走的路径。只有这样,阿里巴巴多年积累的容器技术经验以及更多的智能化能力,才能更好地向全社会开放,为全社会服务。获得 containerd 社区席位,是一种荣耀,同样也是一份责任。未来,阿里巴巴的工程师将致力于做好国内容器市场与全球 containerd 社区的桥梁,将中国声音传递至云原生社区,并让更多从业人员享受 containerd 以及容器标准发展的红利。本文作者:amber涂南阅读原文本文为云栖社区原创内容,未经允许不得转载。

December 7, 2018 · 1 min · jiezi

盘点:2018年双11背后的蚂蚁核心技术

摘要: 一起来探索蚂蚁双11的神秘技术之旅吧!小蚂蚁说:你们都很关心的 “OB双11大促实战分享” 专题来啦!本系列将为你系统性的介绍OceanBase支撑蚂蚁双11背后的技术原理和实战分享。从平台到架构,再到实现,一起来探索蚂蚁双11这场神秘的技术之旅吧!2018年的双11十周年,最终成交额以2135亿元创纪录收官,支付宝系统在这场“商业奥运会”中再次经受住了考验。这也是OceanBase顺利支撑蚂蚁双11的第五年。从五年前,只有10%流量切到OceanBase上,到如今OceanBase 2.0版本成功支撑2018年双11的支付宝核心链路。每年不变的是一如既往的表现平稳,丝般顺滑,变化的是技术能力的不断升级和迭代。今年的双11,OceanBase 2.0扛起了大梁,性能比去年提升了50%,真正实现了“零成本”支撑大促。一、2018双11大促使用了哪些核心技术?今年的双11,OceanBase致力于通过底层架构及平台能力的提升,来实现双11稳定性、成本优化、性能及效率方面的全方位的提升。相较以往始终如一“丝般顺滑”的大促能力外,2018年的双11,OceanBase更加注重长久技术能力的沉淀:OceanBase2.0版本首次上线支付宝的核心链路,包括交易、支付系统,为“峰值百万支付能力”的三年战略沉淀了通用的“极致弹性”的分布式数据库能力,夯实了百万支付的底层基座。在底层存储介质方面,OceanBase 2.0核心链路首次100%运行在容器上,同时存储计算分离架构上线,大幅降低资源成本的同时夯实统一存储基座。在智能化运维的实践方面,OCP(OceanBase云平台)着眼于SQL优化诊断、故障根因分析和智能容量规划等数据库关键场景,将数据库专家的经验与AI算法/机器学习相结合,提供智能化的数据库服务。在平台能力的沉淀上,OCP引入Orchestration理念,通过编排/复用原子变更任务灵活,实现大促快速弹出/弹回的流程,同时平台内置变更免疫及变更三板斧能力(可监控/可灰度/可回滚),极大的提升了大促整体的稳定性和效率;在整个大促期间,OCP自动执行40000+变更,最终实现全程零故障。在商业产品化方面:智能化运维及平台能力抽象出大促及对外商业化场景,建设通用能力来覆盖蚂蚁内外场景。二、OceanBase 2.0 & 百万支付每年双11的压力在不断创造新高,支付系统需要具备百万每秒的支付能力,那么一个亟待解决的问题是:如何解决最小数据分片的峰值能力超过单机性能的问题。OceanBase 2.0应运而生,其目标是在应用无感知的情况下对数据分片进一步拆分,将数据sharding到无限多的机器上,实现极致弹性能力优雅支撑百万支付峰值。1.百万支付架构如下图的百万支付架构所示,传统数据库的弹性架构,将数据进行物理拆分到不同机器,业务在数据访问、研发、后期维护及数据配套设施上都非常繁琐;同时拆分后资源很难快速回收,且数据拆分及聚合无法实现业务无损。相比于传统数据库的弹性架构,OceanBase 2.0架构完全不侵入业务,内部通过分区实现数据分片的自组织及负载均衡,通过生成列及分区规则实现自动路由,通过分区聚合(partition_group)消除分布式事务性能开销以提升性能,从而实现无损线性伸缩。另外,数据分片间share_nothing及多版本的架构,实现分片故障隔离及单点故障消除的高可用架构。2.性能提升为实现“零成本大促”,OceanBase 2.0花了非常多的精力致力于性能的提升。相比OceanBase1.0,2.0在分布式架构上全面升级,如原生sharding/分布式事务优化/优化事务提交日志开销。OceanBase作为底层基础软件,任何微小的性能提升都会为业务节省大量资源,秉承持续优化的匠心,OceanBase 2.0在数据库底层架构、系统实现层面及数据库运行环境全方位进行优化。最终,OceanBase 2.0相比1.0提升了50%的性能,实现今年双11大促的零机器增加。三、OceanBase 容器化 & 存储计算分离双11峰值需要大量的资源支撑,而峰值后资源处于低水位状态,如何快速申请/释放这部分资源?双11当天非支付链路资源空闲,大促是否可以抢占这批资源?大促不同活动时间错峰,不同链路的资源可否实现快速腾挪?类似的资源问题不一而足。大家可以发现以上问题的本质在于:如何最大化程度降低双11当天的资源成本?这是大促技术要实现的一个核心价值。双11大促资源成本与两个因素相关,一个是大促资源的总数目,另一个是持有时长。我们可以通过系统优化提升单机性能,来降低大促资源的总数目(如前章节提到的OceanBase 2.0的性能优化)。那么如何降低持有时长呢?我们统一的思路是:用“高峰期抢占/低峰值释放资源”的方式来大幅降低持有时长;其两个关键前提技术就是容器化和存储计算分离。1.OceanBase容器化OceanBase容器化的核心思想是“资源调度”,大促目标就是“OceanBase能够被快速调度到各种资源载体上(如离线资源、云资源、峰值无压力的数据库其他集群)”;容器化屏蔽了底层资源载体的差异化,具备弹性部署高效的优点,是资源调度的前提条件。OceanBase打造自身调度能力,深入结合副本、租户的概念,精细化资源画像,使得OB容器化部署快速实现分时复用、资源抢占及混部。2.存储计算分离存储计算分离,顾名思义,将数据库运行依赖的计算资源和存储资源部署到不同的资源载体上,从而实现数据库的弱状态化,使得数据库可分别对存储和计算资源进行弹性伸缩。其好处是显而易见的。典型场景:大促态——CPU资源需求激增,而存储资源增幅很小,那么我们可以针对性对计算资源的机型进行扩容,从而降低资源成本且提升扩容效率;日常态——OB LSM架构将离散IO转化成顺序IO,因此存储的IO能力不是瓶颈,更多的是存储空间上的需求;存储计算分离后,多集群间可降低存储碎片,共享整体存储资源池,提升资源利用率。四、平台智能化随着业务规模的快速增长,系统稳定性SLA预发严峻和OceanBase部署的多样化,传统平台已无法满足我们的需求,可以预见不久的将来,运维将成为业务扩展的瓶颈。因此,OceanBase平台正在逐步走向智能化道路实现智能运维。OCP着眼于SQL优化诊断、故障根因分析和智能容量等大促关键场景,目标是将运维专家的技术经验和AI算法/机器学习技术相结合,分解运维关键技术,开发成一系列的智能运维模型,应用于大规模运维系统中。众所周知,SQL plan的正确性对数据库运行至关重要。OCP针对风险场景SQL,在千万峰值压力下,实时进行plan正确性比对,并对可能存在性能变坏隐患的SQL进行分钟级修正。容量水位是大促至关重要的一环,OCP通过数据建模/智能水位预测对集群/租户/docker进行容量画像,结合OceanBase内置Tenant Group能力,实现容器/集群/租户等多个维度的自动扩缩容,同时计算容量plan在集群/租户维度混部,实现最佳负载均衡部署【 深度部署资源利用率达到(n-1)/n 】,大幅节省了机器资源。OCP作为OceanBase的“智能大脑”,实时监控数据库运行状态,小至单条SQL plan,大至数千台机器容量,真正做到了生产环境智能化全覆盖。未来,OCP还将不断创新数据库智能化的运维之路,打造更加完善的数据库自治体系。五、生态与连接蚂蚁金服与金融机构最早建立的连接是基于支付业务的合作,后来又逐渐扩展了很多其他普惠金融类的业务,比如网商银行的同业合作,借呗/花呗等。如今随着在蚂蚁金服内部多年积累的技术能力与产品能力,OceanBase也将全面走向外部,对所有行业开放,通过科技作为新的连接纽带助力企业的数字化转型。过去金融业IT系统的基础架构建设基本都来自国外,如IBM、甲骨文、EMC这些公司构建底层架构,其中门槛最高的就是数据库的整体平滑替换。OceanBase团队从成立之初就肩负着使命,即我们要做一款通用数据库真正的去推动整个社会的进步,能够让整个社会的生产力发生变化。从2016年底,OceanBase就开始准备走出去,用技术改变业务形态;用技术创造新的业务模式,与更多企业建立更为紧密的连接关系。近两年对外服务的过程中,通过与ISV的深度合作与赋能,不仅提供OceanBase内核的能力,也不断丰富周边配套产品生态,涵盖使用数据库过程中的方方面面。未来,我们将继续致力于提供高可用、高性能、低成本的数据库服务,相信通过科技的连接助力更多企业,让科技的产出变成可以量化的业务价值。本文作者:平生栗子阅读原文本文为云栖社区原创内容,未经允许不得转载。

December 4, 2018 · 1 min · jiezi

手把手教您将libreoffice移植到函数计算平台

LibreOffice是由文档基金会开发的自由及开放源代码的办公室套件.LibreOffice套件包含文字处理器,电子表格,演示文稿程序,数量库管理程序及创建和编辑数学公式的应用程序。借助LibreOffice的命令行接口可以方便地将office文件转换成pdf。如下所示:$ soffice –convert-to pdf –outdir /tmp /tmp/test.doc一个完整版本的LibreOffice大小为2 GB,而函数计算运行时缓存目录/ tmp空间限制为512M,zip程序包大小限制为50M。好在社区已经有项目aws-lambda-libreoffice成功的将libreoffice移植到AWS Lambda平台,基于前人的方法和经验,本人创建了fc-libreoffice项目,使libreoffice成功的运行在阿里云函数计算平台.fc -libreoffice 在aws-lambda-libreoffice的基础上解决了如下问题:重新编译和裁剪libreoffice,使其适配FC nodejs8 runtime内置的gcc和内核版本;安装运行时缺失的libssl3依赖;借助OSS运行时下载解压,以绕过zip程序包50M的限制;制作了一个例子项目,支持一键部署,快速体验。本文侧重于记述整个移植过程,记录关键步骤以备忘,也为类似的转换工具移植到函数计算平台提供参考。如果您对于如何快速搭建一个廉价且可扩展的词转换pdf云服务更感兴趣,可以阅读另一篇文章“五分钟上线 - 函数计算Word转PDF云服务”。准备工作在开始之前建议找一个台配置较好的Debain / Ubuntu机器,libreoffice编译比较消耗计算资源。并在机器上安装和配置如下工具:docker-ce安装方法参考官方安装文档好玩的一款函数计算的编排工具,用于快速部署函数计算应用。MacOS平台可以使用如下方法安装brew tap vangie/formulabrew install fun其他平台可以通过npm安装npm install @alicloud/fun -gossutil oss的命令行工具。将其下载并放置到&dollar; PATH所在目录。编译libreoffice我们会采用fc-aliyunfc/runtime-nodejs8:build docker 提供的docker镜像进行编译.fc-docker提供了一系列的docker镜像,这些docker镜像环境非常接近函数计算的真实环境。因为我们打算把libreoffice跑在nodejs8环境中,所以我们选用了aliyunfc/runtime-nodejs8:build,建造标签镜像相比于其他镜像会多一些构建需要的基础包。启动一个编译环境通过如下命令可启动一个用于构建libreoffice的容器。docker run –name libre-builder –rm -v $(pwd):/code -d -t –cap-add=SYS_PTRACE –security-opt seccomp=unconfined aliyunfc/runtime-nodejs8:build bash上面的命令,我们启动了一个名为libre-builder的容器并把当前目录挂载到容器内文件系统的/代码目录。附加参数–cap-add=SYS_PTRACE –security-opt seccomp=unconfined是cpp程序编译需要的,否则会报出一些警告。-d表示以后台daemon的方式启动。-t表示启动tty,配合后面的bash命令是为了卡主容器不退出。而–rm表示一旦容器停止了就自动删除容器。安装编译工具接下来进入容器安装编译工具apt-get install -y ccacheapt-get build-dep -y libreofficeccache是一个编译工具,可以加速gcc对同一个程序的多次编译。尽管第一次编译会花费长一点的时间,有了ccache,后续的编译将变得非常非常快。apt-get的build-dep子命令会建立某个要编译软件的环境。具体行为就是把所有依赖的工具和软件包都安装上。克隆源码git clone –depth=1 git://anongit.freedesktop.org/libreoffice/core libreofficecd libreoffice记得加上–depth=1参数,因为libreoffice项目比较大,进行全量克隆会比较费时间,对于编译来说git提交历史没有意义。配置并编译# 如果多次编译,该设置可以加速后续编译ccache –max-size 16 G && ccache -s通过–disable参数去掉不需要的模块,以减少最终编译产物的体积。# the most important part. Run ./autogen.sh –help to see wha each option means./autogen.sh –disable-report-builder –disable-lpsolve –disable-coinmp \ –enable-mergelibs –disable-odk –disable-gtk –disable-cairo-canvas \ –disable-dbus –disable-sdremote –disable-sdremote-bluetooth –disable-gio –disable-randr \ –disable-gstreamer-1-0 –disable-cve-tests –disable-cups –disable-extension-update \ –disable-postgresql-sdbc –disable-lotuswordpro –disable-firebird-sdbc –disable-scripting-beanshell \ –disable-scripting-javascript –disable-largefile –without-helppack-integration \ –without-system-dicts –without-java –disable-gtk3 –disable-dconf –disable-gstreamer-0-10 \ –disable-firebird-sdbc –without-fonts –without-junit –with-theme=“no” –disable-evolution2 \ –disable-avahi –without-myspell-dicts –with-galleries=“no” \ –disable-kde4 –with-system-expat –with-system-libxml –with-system-nss \ –disable-introspection –without-krb5 –disable-python –disable-pch \ –with-system-openssl –with-system-curl –disable-ooenv –disable-dependency-tracking开始编译make的名单最终compile-查询查询结果位于./instdir/目录下。精简尺寸使用strip命令去除二进制文件中的符号信息和编译信息# this will remove ~100 MB of symbols from shared objectsstrip ./instdir/**/删除不必要的文件# remove unneeded stuff for headless moderm -rf ./instdir/share/gallery \ ./instdir/share/config/images_.zip \ ./instdir/readmes \ ./instdir/CREDITS.fodt \ ./instdir/LICENSE* \ ./instdir/NOTICE验证使用如下命令,测试一下编译出来的soffice是否能正常将txt文件转换成pdf文件。echo “hello world” > a.txt./instdir/program/soffice –headless –invisible –nodefault –nofirststartwizard \ –nolockcheck –nologo –norestore –convert-to pdf –outdir $(pwd) a.txt打包# archivetar -zcvf lo.tar.gz instdir然后使用如下命令将lo.tar.gz文件从容器文件系统拷贝到宿主机文件系统。docker cp libre-builder:/code/libreoffice/lo.tar.gz ./lo.tar.gzGzip vs Zopfli vs Brotli Gzip,Zopfli和Brotli是三种开源的压缩算法,对于一个130M的铬文件,分别采用这三种压缩算法最大级别的压缩效果是文件算法MIB压缩比解压耗时铬-130.62–chromium.gzGzip已44.1366.22%0.968schromium.gzZopfli43.0067.08%0.935schromium.brBrotli33.2174.58%0.712s从上面的结果看Brotli算法的效果最优。由于aliyunfc/runtime-nodejs8:build是基于debain jessie发行版的。在debain jessie上安装brotli较为麻烦,所以我们借助ubuntu容器安装brotli工具,将tar.gz格式转为tar.br格式。docker run –name brotli-util –rm -v $(pwd):/root -w /root -d -t ubuntu:18.04 bashdocker exec -t brotli-util apt-get updatedocker exec -t brotli-util apt-get install -y brotlidocker exec -t brotli-util gzip -d lo.tar.gzdocker exec -t brotli-util brotli -q 11 -j -f lo.tar然后当前目录会多一个lo.tar.br文件。安装依赖在函数计算nodejs8环境中运行soffice,需要安装通过npm安装tar.br的解压依赖包@shelf/aws-lambda-brotli-unpacker 和通过apt-get安装libnss3依赖。先启动一个nodejs8的容器,以保证依赖的安装环境和运行时环境是一致的。docker run –rm –name libreoffice-builder -t -d -v $(pwd):/code –entrypoint /bin/sh aliyunfc/runtime-nodejs8注意:存在@shelf/aws-lambda-brotli-unpacker本机绑定,所以在开发机MacOS上npm install打包上传是无法工作。docker exec -t libreoffice-builder npm install由于函数计算运行时无法安装全局的deb包,所以需要将deb和依赖的deb包下载下来,再安装到当前工作目录而不是系统目录。当前工作目录下可以随代码一起打包上传。docker exec -t libreoffice-builder apt-get install -y -d -o=dir::cache=/code libnss3docker exec -t libreoffice-builder bash -c ‘for f in $(ls /code/archives/.deb); do dpkg -x $f $(pwd) ; done;’libnss3包含了许多.so动态链接库文件,linux系统下LD_LIBRARY_PATH环境变量里的动态链接库才能被找到,而函数计算将代码目录/代码下的lib目录默认添加到了LD_LIBRARY_PATH中。所以我们写个脚本,把所有安装的.so文件软连接到/ code / lib目录下docker exec -t libreoffice-builder bash -c “rm -rf /code/archives/; mkdir -p /code/lib;cd /code/lib; find ../usr/lib -type f ( -name ‘.so’ -o -name ‘*.chk’ ) -exec ln -sf {} . ;“下载并解压tar.br为了使用这个lo.tar.br文件,需要先上传到OSSossutil cp $SCRIPT_DIR/../node_modules/fc-libreoffice/bin/lo.tar.br oss://${OSS_BUCKET}/lo.tar.br \ -i ${ALIBABA_CLOUD_ACCESS_KEY_ID} -k ${ALIBABA_CLOUD_ACCESS_KEY_SECRET} -e oss-${ALIBABA_CLOUD_DEFAULT_REGION}.aliyuncs.com -f在函数的初始化方法中下载。module.exports.initializer = (context, callback) => { store = new OSS({ region: oss-${process.env.ALIBABA_CLOUD_DEFAULT_REGION}, bucket: process.env.OSS_BUCKET, accessKeyId: context.credentials.accessKeyId, accessKeySecret: context.credentials.accessKeySecret, stsToken: context.credentials.securityToken, internal: process.env.OSS_INTERNAL === ’true’ }); if (fs.existsSync(binPath) === true) { callback(null, “already downloaded.”); return; } co(store.get(’lo.tar.br’, binPath)).then(function (val) { callback(null, val) }).catch(function (err) { callback(err) });};然后借助于@shelf/aws-lambda-brotli-unpackernpm包解压lo.tar.brconst {unpack} = require(’@shelf/aws-lambda-brotli-unpacker’);const {execSync} = require(‘child_process’);const inputPath = path.join(__dirname, ‘..’, ‘bin’, ’lo.tar.br’);const outputPath = ‘/tmp/instdir/program/soffice’;module.exports.handler = async event => { await unpack({inputPath, outputPath}); execSync(${outputPath} --convert-to pdf --outdir /tmp /tmp/example.docx);};有趣部署函数编写一个template.yml文件,将函数计算的配置都写在该文件中,然后使用fun deploy命令部署函数。ROSTemplateFormatVersion: ‘2015-09-01’Transform: ‘Aliyun::Serverless-2018-04-03’Resources: libre-svc: # service name Type: ‘Aliyun::Serverless::Service’ Properties: Description: ‘fc test’ Policies: - AliyunOSSFullAccess libre-fun: # function name Type: ‘Aliyun::Serverless::Function’ Properties: Handler: index.handler Initializer: index.initializer Runtime: nodejs8 CodeUri: ‘./’ Timeout: 60 MemorySize: 640 EnvironmentVariables: ALIBABA_CLOUD_DEFAULT_REGION: ${ALIBABA_CLOUD_DEFAULT_REGION} OSS_BUCKET: ${OSS_BUCKET} OSS_INTERNAL: ’true’真实场景下,把秘钥和一起变量写在template.yml里并不合适。为了做到代码和配置相分离,上面使用了变量占位符${ALIBABA_CLOUD_DEFAULT_REGION}和${OSS_BUCKET}。然后使用envsubst进行替换SCRIPT_DIR=dirname -- "$0"source $SCRIPT_DIR/../.envexport ALIBABA_CLOUD_DEFAULT_REGION OSS_BUCKETenvsubst < $SCRIPT_DIR/../template.yml.tpl > $SCRIPT_DIR/../template.ymlcd $SCRIPT_DIR/../上面所有的配置都写在了.env文件中,dotenv是社区常见的方案,也有广泛的工具支持。小结本文重点介绍了编译libreoffice的过程,这也是移植中较为困难的部分。由于libreoffice又涉及到npm的本地绑定和apt-get安装到本地目录的问题,所以在函数计算依赖方面本例也是非常经典的场景。无论是编译还是依赖安装,本文中的步骤都强烈地依赖fc-docker镜像,正因为有了该镜像,解决了环境差异问题,大大降低了移植的难度。大文件运行时加载也是函数计算的常见问题,对于转换工具场景中常见的大文件是二进制程序,对于机器学习场景中大文件常是训练模型的数据问题,但是无论是哪一种,采用OSS下载解压的方法都是通用的,随着函数计算支持了NAS,使用NAS挂载共享网盘的方式也是一种新的路径。上文完整的源码可以在FC-LibreOffice的项目中找到。参考阅读https://zh.wikipedia.org/wiki/LibreOffice如何在AWS Lambda中运行LibreOffice以获得大规模的廉价PDFhttps://github.com/alixaxel/chrome-aws-lambdahttps://github.com/shelfio/aws-lambda-brotli-unpacker本文作者:倚贤阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 3, 2018 · 3 min · jiezi

如何在优雅地Spring 中实现消息的发送和消费

本文将对rocktmq-spring-boot的设计实现做一个简单的介绍,读者可以通过本文了解将RocketMQ Client端集成为spring-boot-starter框架的开发细节,然后通过一个简单的示例来一步一步的讲解如何使用这个spring-boot-starter工具包来配置,发送和消费RocketMQ消息。通过本文,您将了解到:Spring的消息框架介绍rocketmq-spring-boot具体实现使用示例前言上世纪90年代末,随着Java EE(Enterprise Edition)的出现,特别是Enterprise Java Beans的使用需要复杂的描述符配置和死板复杂的代码实现,增加了广大开发者的学习曲线和开发成本,由此基于简单的XML配置和普通Java对象(Plain Old Java Objects)的Spring技术应运而生,依赖注入(Dependency Injection), 控制反转(Inversion of Control)和面向切面编程(AOP)的技术更加敏捷地解决了传统Java企业及版本的不足。随着Spring的持续演进,基于注解(Annotation)的配置逐渐取代了XML文件配置, 2014年4月1日,Spring Boot 1.0.0正式发布,它基于“约定大于配置”(Convention over configuration)这一理念来快速地开发、测试、运行和部署Spring应用,并能通过简单地与各种启动器(如 spring-boot-web-starter)结合,让应用直接以命令行的方式运行,不需再部署到独立容器中。这种简便直接快速构建和开发应用的过程,可以使用约定的配置并且简化部署,受到越来越多的开发者的欢迎。Apache RocketMQ是业界知名的分布式消息和流处理中间件,简单地理解,它由Broker服务器和客户端两部分组成:其中客户端一个是消息发布者客户端(Producer),它负责向Broker服务器发送消息;另外一个是消息的消费者客户端(Consumer),多个消费者可以组成一个消费组,来订阅和拉取消费Broker服务器上存储的消息。为了利用Spring Boot的快速开发和让用户能够更灵活地使用RocketMQ消息客户端,Apache RocketMQ社区推出了spring-boot-starter实现。随着分布式事务消息功能在RocketMQ 4.3.0版本的发布,近期升级了相关的spring-boot代码,通过注解方式支持分布式事务的回查和事务消息的发送。本文将对当前的设计实现做一个简单的介绍,读者可以通过本文了解将RocketMQ Client端集成为spring-boot-starter框架的开发细节,然后通过一个简单的示例来一步一步的讲解如何使用这个spring-boot-starter工具包来配置,发送和消费RocketMQ消息。Spring 中的消息框架顺便在这里讨论一下在Spring中关于消息的两个主要的框架,即Spring Messaging和Spring Cloud Stream。它们都能够与Spring Boot整合并提供了一些参考的实现。和所有的实现框架一样,消息框架的目的是实现轻量级的消息驱动的微服务,可以有效地简化开发人员对消息中间件的使用复杂度,让系统开发人员可以有更多的精力关注于核心业务逻辑的处理。2.1 Spring MessagingSpring Messaging是Spring Framework 4中添加的模块,是Spring与消息系统集成的一个扩展性的支持。它实现了从基于JmsTemplate的简单的使用JMS接口到异步接收消息的一整套完整的基础架构,Spring AMQP提供了该协议所要求的类似的功能集。 在与Spring Boot的集成后,它拥有了自动配置能力,能够在测试和运行时与相应的消息传递系统进行集成。单纯对于客户端而言,Spring Messaging提供了一套抽象的API或者说是约定的标准,对消息发送端和消息接收端的模式进行规定,不同的消息中间件提供商可以在这个模式下提供自己的Spring实现:在消息发送端需要实现的是一个XXXTemplate形式的Java Bean,结合Spring Boot的自动化配置选项提供多个不同的发送消息方法;在消息的消费端是一个XXXMessageListener接口(实现方式通常会使用一个注解来声明一个消息驱动的POJO),提供回调方法来监听和消费消息,这个接口同样可以使用Spring Boot的自动化选项和一些定制化的属性。如果有兴趣深入的了解Spring Messaging及针对不同的消息产品的使用,推荐阅读这个文件。参考Spring Messaging的既有实现,RocketMQ的spring-boot-starter中遵循了相关的设计模式并结合RocketMQ自身的功能特点提供了相应的API(如,顺序,异步和事务半消息等)。2.2 Spring Cloud StreamSpring Cloud Stream结合了Spring Integration的注解和功能,它的应用模型如下:Spring Cloud Stream框架中提供一个独立的应用内核,它通过输入(@Input)和输出(@Output)通道与外部世界进行通信,消息源端(Source)通过输入通道发送消息,消费目标端(Sink)通过监听输出通道来获取消费的消息。这些通道通过专用的Binder实现与外部代理连接。开发人员的代码只需要针对应用内核提供的固定的接口和注解方式进行编程,而不需要关心运行时具体的Binder绑定的消息中间件。在运行时,Spring Cloud Stream能够自动探测并使用在classpath下找到的Binder。这样开发人员可以轻松地在相同的代码中使用不同类型的中间件:仅仅需要在构建时包含进不同的Binder。在更加复杂的使用场景中,也可以在应用中打包多个Binder并让它自己选择Binder,甚至在运行时为不同的通道使用不同的Binder。Binder抽象使得Spring Cloud Stream应用可以灵活的连接到中间件,加之Spring Cloud Stream使用利用了Spring Boot的灵活配置配置能力,这样的配置可以通过外部配置的属性和Spring Boo支持的任何形式来提供(包括应用启动参数、环境变量和application.yml或者application.properties文件),部署人员可以在运行时动态选择通道连接destination(例如,Kafka的topic或者RabbitMQ的exchange)。Binder SPI的方式来让消息中间件产品使用可扩展的API来编写相应的Binder,并集成到Spring Cloud Steam环境,目前RocketMQ还没有提供相关的Binder,我们计划在下一步将完善这一功能,也希望社区里有这方面经验的同学积极尝试,贡献PR或建议。spring-boot-starter的实现在开始的时候我们已经知道,spring boot starter构造的启动器对于使用者是非常方便的,使用者只要在pom.xml引入starter的依赖定义,相应的编译,运行和部署功能就全部自动引入。因此常用的开源组件都会为Spring的用户提供一个spring-boot-starter封装给开发者,让开发者非常方便集成和使用,这里我们详细的介绍一下RocketMQ(客户端)的starter实现过程。3.1. spring-boot-starter的实现步骤对于一个spring-boot-starter实现需要包含如下几个部分:在pom.xml的定义定义最终要生成的starter组件信息<groupId>org.apache.rocketmq</groupId><artifactId>spring-boot-starter-rocketmq</artifactId><version>1.0.0-SNAPSHOT</version>定义依赖包,它分为两个部分: A、Spring自身的依赖包; B、RocketMQ的依赖包<dependencies> <!– spring-boot-start internal depdencies –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!– rocketmq dependencies –> <dependency> <groupId>org.apache.rocketmq</groupId> <artifactId>rocketmq-client</artifactId> <version>${rocketmq-version}</version> </dependency></dependencies> <dependencyManagement> <dependencies> <!– spring-boot-start parent depdency definition –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>${spring.boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies></dependencyManagement>配置文件类定义应用属性配置文件类RocketMQProperties,这个Bean定义一组默认的属性值。用户在使用最终的starter时,可以根据这个类定义的属性来修改取值,当然不是直接修改这个类的配置,而是spring-boot应用中对应的配置文件:src/main/resources/application.properties.定义自动加载类定义 src/resources/META-INF/spring.factories文件中的自动加载类, 其目的是让spring boot更具文中中所指定的自动化配置类来自动初始化相关的Bean,Component或Service,它的内容如下:org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.apache.rocketmq.spring.starter.RocketMQAutoConfiguration在RocketMQAutoConfiguration类的具体实现中,定义开放给用户直接使用的Bean对象. 包括:RocketMQProperties 加载应用属性配置文件的处理类;RocketMQTemplate 发送端用户发送消息的发送模板类;ListenerContainerConfiguration 容器Bean负责发现和注册消费端消费实现接口类,这个类要求:由@RocketMQMessageListener注解标注;实现RocketMQListener泛化接口。最后具体的RocketMQ相关的封装在发送端(producer)和消费端(consumer)客户端分别进行封装,在当前的实现版本提供了对Spring Messaging接口的兼容方式。3.2. 消息发送端实现普通发送端发送端的代码封装在RocketMQTemplate POJO中,下图是发送端的相关代码的调用关系图:为了与Spring Messaging的发送模板兼容,在RocketMQTemplate集成了AbstractMessageSendingTemplate抽象类,来支持相关的消息转换和发送方法,这些方法最终会代理给doSend()方法;doSend()以及RocoketMQ所特有的一些方法如异步,单向和顺序等方法直接添加到RoketMQTempalte中,这些方法直接代理调用到RocketMQ的Producer API来进行消息的发送。事务消息发送端对于事务消息的处理,在消息发送端进行了部分的扩展,参考下图的调用关系类图:RocketMQTemplate里加入了一个发送事务消息的方法sendMessageInTransaction(), 并且最终这个方法会代理到RocketMQ的TransactionProducer进行调用,在这个Producer上会注册其关联的TransactionListener实现类,以便在发送消息后能够对TransactionListener里的方法实现进行调用。3.3. 消息消费端实现在消费端Spring-Boot应用启动后,会扫描所有包含@RocketMQMessageListener注解的类(这些类需要集成RocketMQListener接口,并实现onMessage()方法),这个Listener会一对一的被放置到DefaultRocketMQListenerContainer容器对象中,容器对象会根据消费的方式(并发或顺序),将RocketMQListener封装到具体的RocketMQ内部的并发或者顺序接口实现。在容器中创建RocketMQ Consumer对象,启动并监听定制的Topic消息,如果有消费消息,则回调到Listener的onMessage()方法。使用示例上面的一章介绍了RocketMQ在spring-boot-starter方式的实现,这里通过一个最简单的消息发送和消费的例子来介绍如何使这个rocketmq-spring-boot-starter。4.1 RocketMQ服务端的准备启动NameServer和Broker要验证RocketMQ的Spring-Boot客户端,首先要确保RocketMQ服务正确的下载并启动。可以参考RocketMQ主站的快速开始来进行操作。确保启动NameServer和Broker已经正确启动。创建实例中所需要的Topics在执行启动命令的目录下执行下面的命令行操作bash bin/mqadmin updateTopic -c DefaultCluster -t string-topic4.2. 编译rocketmq-spring-boot-starter目前的spring-boot-starter依赖还没有提交的Maven的中心库,用户使用前需要自行下载git源码,然后执行mvn clean install 安装到本地仓库。git clone https://github.com/apache/rocketmq-externals.gitcd rocketmq-spring-boot-startermvn clean install4.3. 编写客户端代码用户如果使用它,需要在消息的发布和消费客户端的maven配置文件pom.xml中添加如下的依赖:<properties> <spring-boot-starter-rocketmq-version>1.0.0-SNAPSHOT</spring-boot-starter-rocketmq-version></properties><dependency> <groupId>org.apache.rocketmq</groupId> <artifactId>spring-boot-starter-rocketmq</artifactId> <version>${spring-boot-starter-rocketmq-version}</version></dependency>属性spring-boot-starter-rocketmq-version的取值为:1.0.0-SNAPSHOT, 这与上一步骤中执行安装到本地仓库的版本一致。消息发送端的代码发送端的配置文件application.properties# 定义name-server地址spring.rocketmq.name-server=localhost:9876# 定义发布者组名spring.rocketmq.producer.group=my-group1# 定义要发送的topicspring.rocketmq.topic=string-topic发送端的Java代码import org.apache.rocketmq.spring.starter.core.RocketMQTemplate;…@SpringBootApplicationpublic class ProducerApplication implements CommandLineRunner { // 声明并引用RocketMQTemplate @Resource private RocketMQTemplate rocketMQTemplate; // 使用application.properties里定义的topic属性 @Value("${spring.rocketmq.springTopic}") private String springTopic; public static void main(String[] args){ SpringApplication.run(ProducerApplication.class, args); } public void run(String… args) throws Exception { // 以同步的方式发送字符串消息给指定的topic SendResult sendResult = rocketMQTemplate.syncSend(springTopic, “Hello, World!”); // 打印发送结果信息 System.out.printf(“string-topic syncSend1 sendResult=%s %n”, sendResult); }}消息消费端代码消费端的配置文件application.properties# 定义name-server地址spring.rocketmq.name-server=localhost:9876# 定义发布者组名spring.rocketmq.consumer.group=my-customer-group1# 定义要发送的topicspring.rocketmq.topic=string-topic消费端的Java代码@SpringBootApplicationpublic class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); }}// 声明消费消息的类,并在注解中指定,相关的消费信息@Service@RocketMQMessageListener(topic = “${spring.rocketmq.topic}”, consumerGroup = “${spring.rocketmq.consumer.group}")class StringConsumer implements RocketMQListener<String> { @Override public void onMessage(String message) { System.out.printf(”——- StringConsumer received: %s %f", message); }}这里只是简单的介绍了使用spring-boot来编写最基本的消息发送和接收的代码,如果需要了解更多的调用方式,如: 异步发送,对象消息体,指定tag标签以及指定事务消息,请参看github的说明文档和详细的代码。我们后续还会对这些高级功能进行陆续的介绍。预告:近期还会推出第二篇文章解读springboot框架下消息事务的用法。参考文档1.Spring Boot features-Messaging2.Enterprise Integration Pattern-组成简介3.Spring Cloud Stream Reference Guide4.https://dzone.com/articles/creating-custom-springboot-starter-for-twitter4j本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

November 29, 2018 · 2 min · jiezi

Kubernetes 弹性伸缩全场景解析 (一)- 概念延伸与组件布局

传统弹性伸缩的困境弹性伸缩是Kubernetes中被大家关注的一大亮点,在讨论相关的组件和实现方案之前。首先想先给大家扩充下弹性伸缩的边界与定义,传统意义上来讲,弹性伸缩主要解决的问题是容量规划与实际负载的矛盾。如上图所示,蓝色的水位线表示集群的容量随着负载的提高不断的增长,红色的曲线表示集群的实际的负载真实的变化。而弹性伸缩要解决的就是当实际负载出现激增,而容量规划没有来得及反应的场景。常规的弹性伸缩是基于阈值的,通过设置一个资源缓冲水位来保障资源的充盈,通常15%-30%左右的资源预留是比较常见的选择。换言之就是通过一个具备缓冲能力的资源池用资源的冗余换取集群的可用。这种方式表面上看是没有什么问题的,确实在很多的解决方案或者开源组件中也是按照这种方式进行实现的,但是当我们深入的思考这种实现方案的时候会发现,这种方式存在如下三个经典问题。1. 百分比碎片难题在一个Kubernetes集群中,通常不只包含一种规格的机器,针对不同的场景、不同的需求,机器的配置、容量可能会有非常大的差异,那么集群伸缩时的百分比就具备非常大的迷惑性。假设我们的集群中存在4C8G的机器与16C32G的机器两种不同规格,对于10%的资源预留,这两种规格是所代表的意义是完全不同的。特别是在缩容的场景下,通常为了保证缩容后的集群不处在震荡状态,我们会一个节点一个节点或者二分法来缩容节点,那么如何根据百分比来判断当前节点是处在缩容状态就尤为重要,此时如果大规格机器有较低的利用率被判断缩容,那么很有可能会造成节点缩容后,容器重新调度后的争抢饥饿。如果添加判断条件,优先缩容小配置的节点,则有可能造成缩容后资源的大量冗余,最终集群中可能会只剩下所有的巨石节点。2. 容量的规划炸弹还记得在没有使用容器前,是如何做容量规划的吗?一般会按照应用来进行机器的分配,例如,应用A需要2台4C8G的机器,应用B需要4台8C16G的机器,应用A的机器与应用B的机器是独立的,相互不干扰。到了容器的场景中,大部分的开发者无需关心底层的资源了,那么这个时候容量规划哪里去了呢?在Kubernetes中是通过Request和Limit的方式进行设置,Request表示资源的申请值,Limit表示资源的限制值。既然Request和Limit才是容量规划的对等概念,那么这就代表着资源的实际计算规则要根据Request和Limit才更加准确。而对于每个节点预留资源阈值而言,很有可能会造成小节点的预留无法满足调度,大节点的预留又调度不完的场景。3. 资源利用率困境集群的资源利用率是否可以真的代表当前的集群状态呢?当一个Pod的资源利用率很低的时候,不代表就可以侵占他所申请的资源。在大部分的生产集群中,资源利用率都不会保持在一个非常高的水位,但从调度来讲,资源的调度水位应该保持在一个比较高的水位。这样才能既保证集群的稳定可用,又不过于浪费资源。如果没有设置Request与Limit,而集群的整体资源利用率很高这意味着什么?这表示所有的Pod都在被以真实负载为单元进行调度,相互之间存在非常严重的争抢,而且简单的加入节点也丝毫无法解决问题,因为对于一个已调度的Pod而言,除了手动调度与驱逐没有任何方式可以将这个Pod从高负载的节点中移走。那如果我们设置了Request与Limit而节点的资源利用率又非常高的时候说明了什么呢?很可惜这在大部分的场景下都是不可能的,因为不同的应用不同的负载在不同的时刻资源的利用率也会有所差异,大概率的情况是集群还没有触发设置的阈值就已经无法调度Pod了。弹性伸缩概念的延伸既然基于资源利用率的弹性伸缩有上述已知的三个问题,有什么办法可以来解决呢?随着应用类型的多样性发展,不同类型的应用的资源要求也存在越来越大的差异。弹性伸缩的概念和意义也在变化,传统理解上弹性伸缩是为了解决容量规划和在线负载的矛盾,而现在是资源成本与可用性之间的博弈。如果将常见的应用进行规约分类,可以分为如下四种不同类型:在线任务类型比较常见的是网站、API服务、微服务等常见的互联网业务型应用,这种应用的特点是对常规资源消耗较高,比如CPU、内存、网络IO、磁盘IO等,对于业务中断容忍性差。离线任务类型例如大数据离线计算、边缘计算等,这种应用的特点是对可靠性的要求较低,也没有明确的时效性要求,更多的关注点是成本如何降低。定时任务类型定时运行一些批量计算任务是这种应用的比较常见形态,成本节约与调度能力是重点关注的部分。特殊任务类型例如闲时计算的场景、IOT类业务、网格计算、超算等,这类场景对于资源利用率都有比较高的要求。单纯的基于资源利用率的弹性伸缩大部分是用来解决第一种类型的应用而产生的,对于其他三种类型的应用并不是很合适,那么Kubernetes是如何解决这个问题的呢?kubernetes的弹性伸缩布局Kubernetes将弹性伸缩的本质进行了抽象,如果抛开实现的方式,对于不同应用的弹性伸缩而言,该如何统一模型呢?Kubernetes的设计思路是将弹性伸缩划分为保障应用负载处在容量规划之内与保障资源池大小满足整体容量规划两个层面。简单理解,当需要弹性伸缩时,优先变化的应该是负载的容量规划,当集群的资源池无法满足负载的容量规划时,再调整资源池的水位保证可用性。而两者相结合的方式是无法调度的Pod来实现的,这样开发者就可以在集群资源水位较低的时候使用HPA、VPA等处理容量规划的组件实现实时极致的弹性,资源不足的时候通过Cluster-Autoscaler进行集群资源水位的调整,重新调度,实现伸缩的补偿。两者相互解耦又相互结合,实现极致的弹性。在Kubernetes的生态中,在多个维度、多个层次提供了不同的组件来满足不同的伸缩场景。如果我们从伸缩对象与伸缩方向两个方面来解读Kubernetes的弹性伸缩的话,可以得到如下的弹性伸缩矩阵。cluster-autoscaler:kubernetes社区中负责节点水平伸缩的组件,目前处在GA阶段(General Availability,即正式发布的版本)。HPA:kubernetes社区中负责Pod水平伸缩的组件,是所有伸缩组件中历史最悠久的,目前支持autoscaling/v1、autoscaling/v2beta1与autoscaling/v2beta2,其中autoscaling/v1只支持CPU一种伸缩指标,在autoscaling/v2beta1中增加支持custom metrics,在autoscaling/v2beta2中增加支持external metrics。cluster-proportional-autoscaler:根据集群的节点数目,水平调整Pod数目的组件,目前处在GA阶段。vetical-pod-autoscaler:根据Pod的资源利用率、历史数据、异常事件,来动态调整负载的Request值的组件,主要关注在有状态服务、单体应用的资源伸缩场景,目前处在beta阶段。addon-resizer:根据集群中节点的数目,纵向调整负载的Request的组件,目前处在beta阶段。在这五个组件中,cluster-autoscaler、HPA、cluster-proportional-autoscaler是目前比较稳定的组件,建议有相关需求的开发者进行选用。对于百分之八十以上的场景,我们建议客户通过HPA结合cluster-autoscaler的方式进行集群的弹性伸缩管理,HPA负责负载的容量规划管理而cluster-autoscaler负责资源池的扩容与缩容。最后在本文中,和大家主要讨论的是在云原生时代下弹性伸缩概念的延伸,以及Kubernetes社区是如何通过解耦的方式通过多个转职的组件实现了两个维度的弹性伸缩,在本系列后面的文章中会为一一解析每个弹性伸缩组件的相关原理与用法。本文作者:莫源阅读原文本文为云栖社区原创内容,未经允许不得转载。

November 23, 2018 · 1 min · jiezi

聊聊Flexbox布局中的flex的演算法

到目前为止,Flexbox布局应该是目前最流行的布局方式之一了。而Flexbox布局的最大特性就是让Flex项目可伸缩,也就是让Flex项目的宽度和高度可以自动填充Flex容器剩余的空间或者缩小Flex项目适配Flex容器不足的宽度。而这一切都是依赖于Flexbox属性中的flex属性来完成。一个Flex容器会等比的按照各Flex项目的扩展比率分配Flex容器剩余空间,也会按照收缩比率来缩小各Flex项目,以免Flex项目溢出Flex容器。但其中Flex项目又是如何计算呢?他和扩展比率或收缩比率之间又存在什么关系呢?在这篇文章中我们将一起来探来。在Flexbox布局中,容器中显示式使用display设置为flex或inline-flex,那么该容器就是Flex容器,而该容器的所有子元素就是Flex项目。简介在这篇文章中,我们将要聊的是有关于flex属性的事情,特别是如何使用该属性来计算Flex项目?在开始之前,先来简单的了解一下flex属性。在Flexbox中,flex属性是flex-grow(扩展比率)、flex-shrink(收缩比率)和flex-basis(伸缩基准)三个属性的简称。这三个属性可以控制一个Flex项目(也有人称为Flex元素),主要表现在以下几个方面:flex-grow:Flex项目的扩展比率,让Flex项目得到(伸张)多少Flex容器多余的空间(Positive free space)flex-shrink:Flex项目收缩比率,让Flex项目减去Flex容器不足的空间(Negative free space)flex-basis:Flex项目未扩展或收缩之前,它的大小是多少在Flexbox布局中,只有充分理解了这三个属性才能彻底的掌握Flex项目是如何扩展和收缩的,也才能更彻底的掌握Flexbox布局。因此掌握这三个属性,以及他们之间的计算关系才是掌握Flexbox布局的关键所在。相关概念在具体介绍flex相关的技术之前,先对几个概念进行描述,因为理解了这几个概念更有易于大家对后面知识的理解。主轴长度和主轴长度属性Flex项目在主轴方向的宽度或高度就是Flex项目的主轴长度,Flex项目的主轴长度属性是width或height属性,具体是哪一个属性,将会由主轴方向决定。剩余空间和不足空间在Flexbox布局中,Flex容器中包含一个或多个Flex项目(该容器的子元素或子节点)。Flex容器和Flex项目都有其自身的尺寸大小,那么就会有:Flex项目尺寸大小之和大于或小于Flex容器 情景:当所有Flex项目尺寸大小之和小于Flex容器时,Flex容器就会有多余的空间没有被填充,那么这个空间就被称为Flex容器的剩余空间(Positive Free Space)当所有Flex项目尺寸大小之和大于Flex容器时,Flex容器就没有足够的空间容纳所有Flex项目,那么多出来的这个空间就被称为负空间(Negative Free Space)举个例子向大家阐述这两个情形:“假设我们有一个容器(Flex容器),显式的给其设置了width为800px,padding为10px,并且box-sizing设置为border-box”。根据CSS的盒模型原理,我们可以知道Flex容器的内宽度(Content盒子的宽度)为800px - 10px * 2 = 780px:假设Flex容器中包含了四个Flex项目,而且每个Flex项目的width都为100px,那么所有Flex项目的宽度总和则是100px * 4 = 400px(Flex项目没有设置其他任何有关于盒模型的尺寸),那么Flex容器将会有剩余的空间出来,即780px - 400px = 380px。这个380px就是我们所说的Flex容器的剩余空间:假设把Flex项目的width从100px调到300px,那么所有Flex项目的宽度总和就变成了300px * 4 = 1200px。这个时候Flex项目就溢出了Flex容器,这个溢出的宽度,即1200px - 780px = 420px。这个420px就是我们所说的Flex容器的不足空间:上面演示的是主轴在x轴方向,如果主轴变成y轴的方向,同样存在上述两种情形,只不过把width变成了height。接下来的内容中,如果没有特殊说明,那么所看到的示例都仅演示主轴在x轴的方向,即flex-direction为row!min-content 和 max-contentmin-content和max-content是CSS中的一个新概念,隶属于CSS Intrinsic and Extrinsic Sizing Specification模块。简单的可以这么理解。CSS可以给任何一个元素显式的通过width属性指定元素内容区域的宽度,内容区域在元素padding、border和margin里面。该属性也是CSS盒模型众多属性之一。记住,CSS的box-sizing可以决定width的计算方式。如果我们显式设置width为关键词auto时,元素的width将会根据元素自身的内容来决定宽度。而其中的min-content和max-content也会根据元素的内容来决定宽度,只不过和auto有较大的差异min-content: 元素固有的最小宽度max-content: 元素固有的首选宽度比如下面这个示例:如果内容是英文的话,min-content的宽度将取决于内容中最长的单词宽度,中文就有点怪异(其中之因目前并未深究),而max-content则会计算内容排整行的宽度,有点类似于加上了white-space:nowrap一样。上例仅展示了min-content和max-content最基本的渲染效果(Chrome浏览器渲染行为)。这里不做深入的探讨论,毕竟不是本文的重点,如果感兴趣,欢迎关注后续的相关更新,或者先阅读@张鑫旭 老师写的一篇文章《理解CSS3 max/min-content及fit-content等width值》回到我们自己的主题上来。前面在介绍Flex剩余空间和不足空间的时候,我们可以得知,出现这两种现象取决于Flex容器和Flex项目的尺寸大小。而flex属性可以根据Flex容器的剩余空间(或不足空间)对Flex项目进行扩展(或收缩)。那么为了计算出有多少Flex容器的剩余空间能用于Flex项目上,客户端(浏览器)就必须知道Flex项目的尺寸大小。要是没有显式的设置元素的width属性,那么问题就来了,浏览器它是如何解决没有应用于绝对单位的宽度(或高度)的Flex项目,即如何计算?这里所说的min-content和max-content两个属性值对于我们深入的探讨flex属性中的flex-grow和 flex-grow属性有一定的影响。所以提前向大家简单的阐述一正是这两个属性值在浏览器中的渲染行为。简单的总结一下:min-content的大小,从本质上讲,是由字符串中最长的单词决定了大小;max-content则和min-content想反. 它会变得尽可能大, 没有自动换行的机会。如果Flex容器太窄, 它就会溢出其自身的盒子!Flex项目的计算在Flexbox布局当中,其中 flex-grow、flex-shrink和flex-basis都将会影响Flex项目的计算。接下来我们通过一些简单的示例来阐述这方面的知识。flex-basisflex-basis属性在任何空间分配发生之前初始化Flex项目的尺寸。其默认值为auto。如果flex-basis的值设置为auto,浏览器将先检查Flex项目的主尺寸是否设置了绝对值再计算出Flex项目的初始值。比如说,你给Flex项目设置的width为200px,那么200px就是Flex项目的flex-basis值。如果你的Flex项目可以自动调整大小,则auto会解析为其内容的大小,这个时候,min-content和max-content变会起作用。此时将会把Flex项目的max-content作为 flex-basise的值。比如,下面这样的一个简单示例:flex-grow和flex-shrink的值都为0,第一个Flex项目的width为150px,相当于flex-basis的值为150px,而另外两个Flex项目在没有设置宽度的情况之下,其宽度由内容的宽度来设置。如果flex-basis的值设置为关键词content,会导致Flex项目根据其内容大小来设置Flex项目,叧怕是Flex项目显式的设置了width的值。到目前为止,content还未得到浏览器很好的支持。flex-basis除了可以设置auto、content、fill、max-content、min-content和fit-content关键词之外,还可以设置<length>值。如果<length>值是一个百分比值,那么Flex项目的大小将会根据Flex容器的width进行计算。比如下面这个示例:Flex容器显式设置了width(和box-sizing取值有关系,上图为border-box的示例结果),那么flex-basis会根据Flex容器的width计算出来,如果Flex容器未显示设置width值,则计算出来的结果将是未定义的(会自动根据Flex容器的宽度进行计算)。在Flexbox布局中,如果你想完全忽略Flex项目的尺寸,则可以将flex-basis设置为0。这样的设置,基本上是告诉了浏览器,Flex容器所有空间都可以按照相关的比例进行分配。来看一个简单的示例,Flex项目未显式设置width情况之下,flex-basis不同取值的渲染效果。到写这篇文章为止,使用Firefox浏览器查看效果更佳。当Flex项目显式的设置了min-width或max-width的值时,就算Flex项目显式的设置了flex-basis的值,也会按min-width和max-width设置Flex项目宽度。当计算的值大于max-width时,则按max-width设置Flex项目宽度;当计算的值小于min-width时,则按min-width设置Flex项目宽度:有关于flex-basis属性相关的运用简单的小结一下:flex-basis默认值为auto如果Flex项目显式的设置了width值,同时flex-basis为auto时,则Flex项目的宽度为按width来计算,如果未显式设置width,则按Flex项目的内容宽度来计算如果Flex项目显式的设置了width值,同时显式设置了flex-basis的具体值,则Flex项目会忽略width值,会按flex-basis来计算Flex项目当Flex容器剩余空间不足时,Flex项目的实际宽度并不会按flex-basis来计算,会根据flex-grow和flex-shrink设置的值给Flex项目分配相应的空间对于Flexbox布局中,不建议显式的设置Flex项目的width值,而是通过flex-basis来控制Flex项目的宽度,这样更具弹性如果Flex项目显式的设置了min-width或max-width值时,当flex-basis计算出来的值小于min-width则按min-width值设置Flex项目宽度,反之,计算出来的值大于max-width值时,则按max-width的值设置Flex项目宽度flex-grow前面提到过,flex-grow是一个扩展因子(扩展比例)。其意思是,当Flex容器有一定的剩余空间时,flex-grow可以让Flex项目分配Flex容器剩余的空间,每个Flex项目将根据flex-grow因子扩展,从而让Flex项目布满整个Flex容器(有效利用Flex容器的剩余空间)。flex-grow的默认值是0,其接受的值是一个数值,也可以是一个小数值,但不支持负值。一旦flex-grow的值是一个大于0的值时,Flex项目就会占用Flex容器的剩余空间。在使用flex-grow时可以按下面的方式使用:所有Flex项目设置相同的flex-grow值每个Flex项目设置不同的flex-grow值不同的设置得到的效果将会不一样,但flex-grow的值始终总量为1,即Flex项目占有的量之和(分子)和分母相同。我们来具体看看flex-grow对Flex项目的影响。当所有的Flex项目具有一个相同的flex-grow值时,那么Flex项目将会平均分配Flex容器剩余的空间。在这种情况之下将flex-grow的值设置为1。比如下面这个示例,Flex容器(width: 800px,padding: 10px)中有四个子元素(Flex项目),显式的设置了flex-basis为150px,根据前面介绍的内容,我们可以知道每个Flex项目的宽度是150px,这样一来,所有Flex项目宽度总和为150px * 4 = 600px。容器的剩余空间为780px - 600px = 180px。当显式的给所有Flex项目设置了flex-grow为1(具有相同的值)。这样一来,其告诉浏览器,把Flex容器剩余的宽度(180px)平均分成了四份,即:180px / 4 = 45px。而flex-grow的特性就是按比例把Flex容器剩余空间分配给Flex项目(当然要设置了该值的Flex项目),就该例而言,就是给每个Flex项目添加了45px,也就是说,此时Flex项目的宽度从150px扩展到了195px(150px + 45px = 195px)。如下图所示:特别声明,如果Flex项目均分Flex容器剩余的空间,只要给Flex项目设置相同的flex-grow值,大于1即可。比如把flex-grow设置为10,就上例而言,把剩余空间分成了40份,每个Flex项目占10份。其最终的效果和设置为1是等效的。上面我们看到的均分Flex容器剩余空间,事实上我们也可以给不同的Flex项目设置不同的flex-grow值,这样一来就会让每个Flex项目根据自己所占的比例来占用Flex容器剩余的空间。比如上面的示例,把Flex项目的flex-grow分别设置为1:2:3:4。也就是说把Flex容器的剩余空间分成了10份(1 + 2 + 3 + 4 = 10),而每个Flex项目分别占用Flex容器剩余空间的1/10、2/10、3/10和4/10。就上例而言,Flex容器剩余空间是180px,按这样的计算可以得知,每一份的长度是180px / 10 = 18px,如此一来,每个Flex项目的宽度则变成:Flex1: 150px + 18px * 1 = 168pxFlex2: 150px + 18px * 2 = 186pxFlex3: 150px + 18px * 3 = 204pxFlex4: 150px + 18px * 4 = 222px最终效果如下图所示:前面两个示例向大家演示了,Flex项目均分和非均分Flex容器剩余的空间。从示例中可以看出来,flex-grow的值都是大于或等于1的数值。事实上,flex-grow还可以设置小数。比如,给所有Flex项目设置flex-grow的值为0.2。由于Flex项目的flex-grow的值都相等,所以扩展的值也是一样的,唯一不同的是,所有的Flex项目并没有把Flex容器剩余空间全部分完。就我们这个示例而言,四个Flex项目的flex-grow加起来的值是0.8,小于1。换句话说,四个Flex项目只分配了Flex容器剩余空度的80%,按上例的数据来计算,即是180px * .8 = 144px(只分去了144px),而且每个Flex项目分得都是36px(144px / 4 = 36px 或者 144px * 0.2 / 0.8 = 36px)。最终效果如下图所示:上面的示例中,flex-basis都显式的设置了值。事实上,flex-grow和flex-basis会相互影响的。这也令我们的Flex项目计算变得复杂化了。比如说,flex-basis的值为auto,而且没有给Flex项目显式的设置width。根据前面的内容我们可以得知,此时Flex项目的大小都取决于其内容的max-content大小。此时Flex容器的剩余的空间将由浏览器根据Flex项目的内容宽度来计算。比如接下来的这个示例,四个Flex项目都是由其内容max-content大小决定。同时将flex-grow都设置为1(均匀分配Flex容器剩余空间)。具体的数据由下图所示(Chrome浏览器计算得出的值):特别注意,不同浏览器对小数位的计算略有差异,上图是在Chrome浏览器下得出的值。所以最终加起来的值略大于Flex容器的宽度708px。针对这样的使用场景,如果你想让所有Flex项目具有相同的尺寸,那么可以显式的设置Flex项目的flex-basis值为0(flex: 1 1 0)。从flex-basis一节中可以得知,当flex-basis值为0时,表示所有空间都可以用来分配,而且flex-grow具有相同的值,因此Flex项目可以获取均匀的空间。如此一来Flex项目宽度将会相同。flex-basis还可以由其他值为设置Flex项目的宽度,这里不再一一演示。感兴趣的同学可以自己根据flex-basis的取值写测试用例。换句话说,如果你理解了前面介绍的flex-basis内容,就能更好的理解flex-grow和flex-basis相结合对Flex项目分配Flex容器剩余空间的计算。也将不会再感到困惑。flex-shrinkflex-shrink和flex-grow类似,只不过flex-shrink是用来控制Flex项目缩放因子。当所有Flex项目宽度之和大于Flex容器时,将会溢出容器(flex-wrap为nowrap时),flex-shrink就可以根据Flex项目设置的数值比例来分配Flex容器的不足空间,也就是按比例因子缩小自身的宽度,以免溢出Flex容器。flex-shrink接收一个<number>值,其默认值为1。也就是说,只要容器宽度不足够容纳所有Flex项目时,所有Flex项目默认都会收缩。如果你不想让Flex项目进行收缩时,可以设置其值为0,此时Flex项目始终会保持原始的fit-content宽度。同样的,flex-shrink也不接受一个负值做为属性值。基于上面的示例,简单的调整一下参数,所有Flex项目都设置了flex: 0 0 300px,可以看到Flex项目溢出了Flex容器:在这个示例中,由于flex-shrink显式的设置了值为0,Flex项目不会进行收缩。如果你想让Flex项目进行收缩,那么可以把flex-shrink设置为1。从上图的结果我们可以看出,当所有Flex项目的flex-shrink都设置为相同的值,比如1,将会均分Flex容器不足空间。比如此例,所有Flex项目的宽度总和是1200px(flex-basis: 300px),而Flex容器宽度是780px(width: 800px,padding: 10px,盒模型是border-box),可以算出Flex容器不足空间为420px(1200 - 780 = 420px),因为所有Flex项目的flex-shrink为1,其告诉浏览器,将Flex容器不足空间均分成四份,那么每份则是105px(420 / 4 = 105px),这个时候Flex项目就会自动缩放105px,其宽度就由当初的300px变成了195px(300 - 105 = 195px)。这个示例演示的是Flex项目设置的值都是相同的值,其最终结果是将会均分Flex容器不足空间。其实flex-shrink也可以像flex-grow一样,为不同的Flex项目设置不同的比例因子。比如1:2:3:4,这个时候Flex项目就不会均分了,而是按自己的比例进行收缩,比例因子越大,收缩的将越多。如下图所示:就上图而言,所有Flex项目的flex-shrink之和为10(1 + 2 + 3 + 4 = 10),此时把Flex容器不足空间420px分成了十份,每一份42px(420 / 10 = 42px),每个Flex项目按照自己的收缩因子相应的去收缩对应的宽度,此时每个Flex项目的宽度就变成:Flex1: 300 - 42 * 1 = 258pxFlex2: 300 - 42 * 2 = 216pxFlex3: 300 - 42 * 3 = 174pxFlex4: 300 - 42 * 4 = 132px按照该原理来计算的话,当某个Flex项目的收缩因子设置较大时,就有可能会出现小于0的现象。基于上例,如果把第四个Flex项目的flex-shrink设置为15。这样一来,四个Flex项目的收缩因子就变成:1:2:3:15。也就是说把Flex容器不足空间分成了21份,每份占据的宽度是20px(420 / 21 = 20px)。那么Flex项目的宽度就会出现0的现象(300 - 15 * 20 = 0)。这个时候会不会出现无空间容纳Flex项目的内容呢?事实上并不会这样:在Flexbox布局当中,会阻止Flex项目元素宽度缩小至0。此时Flex项目会以min-content的大小进行计算,这个大小是它们利用任何可以利用的自动断行机会后所变成的如果某个Flex项目按照收缩因子计算得出宽度趋近于0时,Flex项目将会按照该元素的min-content的大小来设置宽度,同时这个宽度将会转嫁到其他的Flex项目,再按相应的收缩因子进行收缩。比如上例,Flex项目四,其flex-shrink为15,但其宽度最终是以min-content来计算(在该例中,Chrome浏览器渲染的宽度大约是22.09px)。而这个22.09px最终按照1:2:3的比例分配给了Flex项目一至三(Flex1,Flex2和Flex3)。对应的Flex项目宽度就变成:Flex1: 300 - 20 * 1 - 22.09 / 6 * 1 = 276.334pxFlex2: 300 - 20 * 2 - 22.09 / 6 * 2 = 252.636pxFlex3: 300 - 20 * 3 - 22.09 / 6 * 3 = 228.955pxFlex4: min-content,在该例中大约是22.09px对于该情形,计算相对而言就更为复杂一些了。但浏览器会很聪明的帮你处理这些场景,会倾向于给你合理的结果。只不过大家需要知道这样的一个细节,碰到类似的场景才不会一脸蒙逼(^_^)。flex-grow可以设置一个小于0的值,同样的,flex-shrink也可以设置一个小于0的值,比如我们给所有的Flex项目设置flex-shrink的值为0.2,你将看到的结果如下:从结果的示例图中我们可以看出来,当所有Flex项目的收缩因子(flex-shrink)总和小于1时,Flex容器不足空间不会完全分配完,依旧会溢出Flex容器。好比该例,flex-shrink的总和是.8,分配了Flex容器剩余空间420px的80%,即336px(还有84px剩余空间未完全分配完),由于每个Flex项目的收缩因子是相同的,好比前面的示例,都设置了1类似,把分配的空间336px均分为四份,也就是84px,因此每个Flex项目的宽度由当初的300px变成了216px(300 - 84 = 216px)。这个其实和flex-grow类似,只不过flex-shrink只是收缩而以。Flex项目计算公式Flex项目伸缩计算是一个较为复杂的过程,但它们之间还是有据可查。@Chris和@Otree对该方面就有深入的研究。他们给Flex项目的计算总结出了一套计算公式,具体公式如下:@Chris还依据这套公式写了一个JavaScript的案例,来模拟Flex项目计算。flex常见的值大部分情形之下,我们都是使用flex属性来设置Flex项目的伸缩的值。其常见值的效果有:flex: 0 auto和flex:initial,这两个值与flex: 0 1 auto相同,也是初始值。会根据width属性决定Flex项目的尺寸。当Flex容器有剩余空间时,Flex项目无法扩展;当Flex容器有不足空间时,Flex项目收缩到其最小值min-content。flex: auto与flex: 1 1 auto相同。Flex项目会根据width来决定大小,但是完全可以扩展Flex容器剩余的空间。如果所有Flex项目均为flex: auto、flex:initial或flex: none,则Flex项目尺寸决定后,Flex容器剩余空间会被平均分给是flex:a uto的Flex项目。flex: none与flex: 0 0 auto相同。Flex项目根据width决定大小,但是完全不可伸缩,其效果和initial类似,这种情况下,即使在Flex容器空间不够而溢出的情况之下,Flex项目也不会收缩。flex: <positive-number>(正数)与flex: 1 0px相同。该值使Flex项目可伸缩,并将flex-basis值设置为0,导致Flex项目会根据设置的比例因子来计算Flex容器的剩余空间。如果所有Flex项目都使用该模式,则它们的尺寸会正比于指定的伸缩比。默认状态下,伸缩项目不会收缩至比其最小内容尺寸(最长的英文词或是固定尺寸元素的长度)更小。可以靠设置min-width属性来改变这个默认状态。如何掌握Flex项目的大小通过前面的内容介绍,应该可以了解到Flex项目的大小计算是非常的复杂。如果要真正的理解Flex项目是如何工作的话,最为关键的是理解有多少东西参与影响Flex项目。我们可以按下面这样的方式来进行思考。怎么设置Flex项目的基本大小在CSS中设置一个元素的基本大小可以通过width来设置,或者通过min-width或max-width来设置元素的最小或最大宽度,在未来我们还可以通过content、min-content、max-content或fit-content等关键词来设置元素的大小。对于Flex项目,我们还可以通过flex-basis设置Flex项目大小。对于如何设置Flex项目的基本大小,我们可以围绕以下几点来进行思考:flex-basis的值是auto?Flex项目显式的设置了宽度吗?如果设置了,Flex项目的大小将会基于设置的宽度flex-basis的值是auto还是content?如果是auto,Flex项目的大小为原始大小flex-basis的值是0的长度单位吗?如果是这样那这就是Flex项目的大小flex-basis的值是0呢? 如果是这样,则Flex项目的大小不在Flex容器空间分配计算的考虑之内更为具体的可以参阅flex-basis相关的介绍。我们有可用空间吗?如果Flex容器没有剩余空间,Flex项目就不会扩展;如果Flex容器没有不足空间,Flex项目就不会收缩:所有的Flex项目的宽度总和是否小于Flex容器的总宽度? 如果是这样,那么Flex容器有剩余空间,flex-grow会发挥作用, 具体如何发挥作用,可以参阅flex-grow相关的介绍所有的Flex项目的宽度总和是否大于Flex容器的总宽度? 如果是这样,那么Flex容器有不足空间,flex-shrink会发挥作用,具体如何发挥作用,可以参阅flex-shrink相关的介绍分配空间的其他方式如果我们不想把Flex容器的剩余空间扩展到Flex项目中,我们可以使用Flexbox中其他属性,比如justify-content属性来分配剩余空间。当然也可以给Flex项目设置margin值为处理Flex容器剩余空间。不过这一部分没有在这里阐述,如果感兴趣的话,不仿阅读一下Flexbox相关的介绍。总结很久以为,一直以为Flexbox布局中,Flex项目都会根据Flex容器自动计算。而事实上呢?正如文章中介绍的一样,Flex项目的计算是相当的复杂。设置Flex项目大小的值以及flex-basis、flex-grow和flex-shrink的设置都会对其有较大的影响,而且它们的组合场景也是非常的多,并且不同的场景会造成不一样的结果。当然,文章中所介绍的内容或许没有覆盖到所有的场景,但这些基本的演示或许能帮助大家更好的理解Flex项目是如何计算的。最后希望该文对大家有所帮助,如果你有更深的了解欢迎在下面的评论中与我一起分享。如果文章中有不对之处,还望各路大婶拍正。本文作者:大漠_w3cplus阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

November 23, 2018 · 2 min · jiezi

面向容器日志的技术实践

摘要: 本文以 Docker 为例,依托阿里云日志服务团队在日志领域深耕多年积累下的丰富经验,介绍容器日志处理的一般方法和最佳实践。背景自 2013 年 dotCloud 公司开源 Docker 以来,以 Docker 为代表的容器产品凭借着隔离性好、可移植性高、资源占用少、启动迅速等特性迅速风靡世界。下图展示了 2013 年以来 Docker 和 OpenStack 的搜索趋势。容器技术在部署、交付等环节给人们带来了很多便捷,但在日志处理领域却带来了许多新的挑战,包括:如果把日志保存在容器内部,它会随着容器的销毁而被删除。由于容器的生命周期相对虚拟机大大缩短,创建销毁属于常态,因此需要一种方式持久化的保存日志;进入容器时代后,需要管理的目标对象远多于虚拟机或物理机,登录到目标容器排查问题会变得更加复杂且不经济;容器的出现让微服务更容易落地,它在给我们的系统带来松耦合的同时引入了更多的组件。因此我们需要一种技术,它既能帮助我们全局性的了解系统运行情况,又能迅速定位问题现场、还原上下文。日志处理流程本文以 Docker 为例,依托阿里云日志服务团队在日志领域深耕多年积累下的丰富经验,介绍容器日志处理的一般方法和最佳实践,包括:容器日志实时采集;查询分析和可视化;日志上下文分析;LiveTail - 云上 tail -f。容器日志实时采集容器日志分类采集日志首先要弄清日志存在的位置,这里以 Nginx、Tomcat 这两个常用容器为例进行分析。Nginx 产生的日志包括 access.log 和 error.log,根据 nginx Dockerfile 可知 access.log 和 error.log 被分别重定向到了 STDOUT 和 STDERR 上。Tomcat 产生的日志比较多,包括 catalina.log、access.log、manager.log、host-manager.log 等,tomcat Dockerfile 并没有将这些日志重定向到标准输出,它们存在于容器内部。容器产生的日志大部分都可以归结于上述情形。这里,我们不妨将容器日志分成以下两类。容器日志分类定义标准输出通过 STDOUT、STDERR 输出的信息,包括被重定向到标准输出的文本文件。文本日志存在于容器内部并且没有被重定向到标准输出的日志。标准输出使用 logging driver容器的标准输出会由 logging driver 统一处理。如下图所示,不同的 logging driver 会将标准输出写往不同的目的地。通过 logging driver 采集容器标准输出的优势在于使用简单,例如:# 该命令表示在 docker daemon 级别为所有容器配置 syslog 日志驱动dockerd -–log-driver syslog –-log-opt syslog-address=udp://1.2.3.4:1111# 该命令表示为当前容器配置 syslog 日志驱动docker run -–log-driver syslog –-log-opt syslog-address=udp://1.2.3.4:1111 alpine echo hello world缺点除了 json-file 和 journald,使用其他 logging driver 将使 docker logs API 不可用。例如,当您使用 portainer 管理宿主机上的容器,并且使用了上述两者之外的 logging driver,您会发现无法通过 UI 界面观察到容器的标准输出。使用 docker logs API对于那些使用默认 logging driver 的容器,我们可以通过向 docker daemon 发送 docker logs 命令来获取容器的标准输出。使用此方式采集日志的工具包括 logspout、sematext-agent-docker 等。下列样例中的命令表示获取容器自2018-01-01T15:00:00以来最新的5条日志。docker logs –since “2018-01-01T15:00:00” –tail 5 <container-id>缺点当日志量较大时,这种方式会对 docker daemon 造成较大压力,导致 docker daemon 无法及时响应创建容器、销毁容器等命令。采集 json-file 文件默认 logging driver 会将日志以 json 的格式写入宿主机文件里,文件路径为/var/lib/docker/containers/<container-id>/<container-id>-json.log。这样可以通过直接采集宿主机文件来达到采集容器标准输出的目的。该方案较为推荐,因为它既不会使 docker logs API 变得不可用,又不会影响 docker daemon,并且现在许多工具原生支持采集宿主机文件,如 filebeat、logtail 等。文本日志挂载宿主机目录采集容器内文本日志最简单的方法是在启动容器时通过 bind mounts 或 volumes 方式将宿主机目录挂载到容器日志所在目录上,如下图所示。针对 tomcat 容器的 access log,使用命令docker run -it -v /tmp/app/vol1:/usr/local/tomcat/logs tomcat将宿主机目录/tmp/app/vol1挂载到 access log 在容器中的目录/usr/local/tomcat/logs上,通过采集宿主机目录/tmp/app/vol1下日志达到采集 tomcat access log 的目的。计算容器 rootfs 挂载点使用挂载宿主机目录的方式采集日志对应用会有一定的侵入性,因为它要求容器启动的时候包含挂载命令。如果采集过程能对用户透明那就太棒了。事实上,可以通过计算容器 rootfs 挂载点来达到这种目的。和容器 rootfs 挂载点密不可分的一个概念是 storage driver。实际使用过程中,用户往往会根据 linux 版本、文件系统类型、容器读写情况等因素选择合适的 storage driver。不同 storage driver 下,容器的 rootfs 挂载点遵循一定规律,因此我们可以根据 storage driver 的类型推断出容器的 rootfs 挂载点,进而采集容器内部日志。下表展示了部分 storage dirver 的 rootfs 挂载点及其计算方法。Logtail 方案在充分比较了容器日志的各种采集方法,综合整理了广大用户的反馈与诉求后,日志服务团队推出了容器日志一站式解决方案。功能特点logtail 方案包含如下功能:支持采集宿主机文件以及宿主机上容器的日志(包括标准输出和日志文件);支持容器自动发现,即当您配置了采集目标后,每当有符合条件的容器被创建时,该容器上的目标日志将被自动采集;支持通过 docker label 以及环境变量过滤指定容器,支持白名单、黑名单机制;采集数据自动打标,即对收集上来的日志自动加上 container name、container IP、文件路径等用于标识数据源的信息;支持采集 K8s 容器日志。核心优势通过 checkpoint 机制以及部署额外的监控进程保证 at-least-once 语义;历经多次双十一、双十二的考验以及阿里集团内部百万级别的部署规模,稳定和性能方面非常有保障。K8s 容器日志采集和 K8s 生态深度集成,能非常方便地采集 K8s 容器日志是日志服务 logtail 方案的又一大特色。采集配置管理:支持通过 WEB 控制台进行采集配置管理;支持通过 CRD(CustomResourceDefinition)方式进行采集配置管理(该方式更容易与 K8s 的部署、发布流程进行集成)。采集模式:支持通过 DaemonSet 模式采集 K8s 容器日志,即每个节点上运行一个采集客户端 logtail,适用于功能单一型的集群;支持通过 Sidecar 模式采集 K8s 容器日志,即每个 Pod 里以容器的形式运行一个采集客户端 logtail,适用于大型、混合型、PAAS 型集群。关于 Logtail 方案的详细说明可参考文章全面提升,阿里云Docker/Kubernetes(K8S) 日志解决方案与选型对比。查询分析和可视化完成日志采集工作后,下一步需要对这些日志进行查询分析和可视化。这里以 Tomcat 访问日志为例,介绍日志服务提供的强大的查询、分析、可视化功能。快速查询容器日志被采集时会带上 container name、container IP、目标文件路径等信息,因此在查询的时候可以通过这些信息快速定位目标容器和文件。查询功能的详细介绍可参考文档查询语法。实时分析日志服务实时分析功能兼容 SQL 语法且提供了 200 多种聚合函数。如果您有使用 SQL 的经验,能够很容易写出满足业务需求的分析语句。例如:统计访问次数排名前 10 的 uri。* | SELECT request_uri, COUNT() as c GROUP by request_uri ORDER by c DESC LIMIT 10统计当前15分钟的网络流量相对于前一个小时的变化情况。 | SELECT diff[1] AS c1, diff[2] AS c2, round(diff[1] * 100.0 / diff[2] - 100.0, 2) AS c3 FROM (select compare( flow, 3600) AS diff from (select sum(body_bytes_sent) as flow from log))该语句使用同比环比函数计算不同时间段的网络流量。可视化为了让数据更加生动,您可以使用日志服务内置的多种图表对 SQL 计算结果进行可视化展示,并将图表组合成一个仪表盘。下图展示了基于 Tomcat 访问日志的仪表盘,它展示了错误请求率、网络流量、状态码随时间的变化趋势等信息。该仪表盘展现的是多个 Tomcat 容器数据聚合后的结果,您可以使用仪表盘过滤器功能,通过指定容器名查看单个容器的数据。日志上下文分析查询分析、仪表盘等功能能帮助我们把握全局信息、了解系统整体运行情况,但定位具体问题往往需要上下文信息的帮助。上下文定义上下文指的是围绕某个问题展开的线索,如日志中某个错误的前后信息。上下文包含两个要素:最小区分粒度:区分上下文的最小空间划分,例如同一个线程、同一个文件等。这一点在定位问题阶段非常关键,因为它能够使得我们在调查过程中避免很多干扰。保序:在最小区分粒度的前提下,信息的呈现必须是严格有序的,即使一秒内有几万次操作。下表展示了不同数据源的最小区分粒度。分类最小区分粒度单机文件IP + 文件Docker 标准输出Container + STDOUT/STDERRDocker 文件Container + 文件K8s 容器标准输出Namespace + Pod + Container + STDOUT/STDERRK8s 容器文件Namespace + Pod + Container + 文件SDK线程Log Appender线程上下文查询面临的挑战在日志集中式存储的背景下,采集端和服务端都很难保证日志原始的顺序:在客户端层面,一台宿主机上运行着多个容器,每个容器会有多个目标文件需要采集。日志采集软件需要利用机器的多个 cpu 核心解析、预处理日志,并通过多线程并发或者单线程异步回调的方式处理网络发送的慢 IO 问题。这使得日志数据不能按照机器上的事件产生顺序依次到达服务端。在服务端层面,由于水平扩展的多机负载均衡架构,使得同一客户端机器的日志会分散在多台存储节点上。在分散存储的日志基础上再恢复最初的顺序是困难的。原理日志服务通过给每条日志附加一些额外的信息以及服务端的关键词查询能力巧妙地解决了上述难题。原理如下图所示。日志被采集时会自动加入用于标识日志来源的信息(即上文提到的最小区分粒度)作为 source_id。针对容器场景,这些信息包括容器名、文件路径等;日志服务的各种采集客户端一般会选择批量上传日志,若干条日志组成一个数据包。客户端会向这些数据包里写入一个单调递增的 package_id,并且包内每条日志都拥有包内位移 offset;服务端会将 source_id、package_id、offset 组合起来作为一个字段并为其建立索引。这样,即使各种日志在服务端是混合存储的状态,我们也可以根据 source_id、package_id、offset 精确定位某条日志。想了解更多有关上下文分析的功能可参考文章上下文查询、分布式系统日志上下文查询功能。LiveTail - 云上 tail -f除了查看日志的上下文信息,有时我们也希望能够持续观察容器的输出。传统方式下表展示了传统模式下实时监控容器日志的方法。类别步骤标准输出1. 定位容器,获取容器 id;2. 使用命令docker logs –f <container id>或kubectl logs –f <pod name>在终端上观察输出;3. 使用grep或grep –v过滤关键信息。 || 文本日志 | 1. 定位容器,获取容器 id;2. 使用命令docker exec或kubectl exec进入容器;3. 找到目标文件,使用命令tail –f观察输出;4. 使用grep或grep –v过滤关键信息。 |痛点通过传统方法监控容器日志存在以下痛点:容器很多时,定位目标容器耗时耗力;不同类型的容器日志需要使用不同的观察方法,增加使用成本;关键信息查询展示不够简单直观。功能和原理针对这些问题,日志服务推出了 LiveTail 功能。相比传统模式,它有如下优点:可以根据单条日志或日志服务的查询分析功能快速定位目标容器;使用统一的方式观察不同类型的容器日志,无需进入目标容器;支持通过关键词进行过滤;支持设置关键列。在实现上,LiveTail 主要用到了上一章中提到的上下文查询原理快速定位目标容器和目标文件。然后,客户端定期向服务端发送请求,拉取最新数据。视频样例您还可以通过观看视频,进一步理解容器日志的采集、查询、分析和可视化等功能。参考资料LC3视角:Kubernetes下日志采集、存储与处理技术实践 - https://yq.aliyun.com/articles/606248全面提升,阿里云Docker/Kubernetes(K8S) 日志解决方案与选型对比 - https://yq.aliyun.com/articles/448676本文作者:吴波bruce_wu阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

November 21, 2018 · 2 min · jiezi

深度解读阿里巴巴云原生镜像分发系统 Dragonfly

Dragonfly 是一个由阿里巴巴开源的云原生镜像分发系统,主要解决以 Kubernetes 为核心的分布式应用编排系统的镜像分发难题。随着企业数字化大潮的席卷,行业应用纷纷朝微服务架构演进,并通过云化平台优化业务管理。Dragonfly 源于阿里巴巴,从实际落地场景出发,前瞻性地解决了云原生镜像分发的__效率、流控与安全__三大难题。Dragonfly 目前承载了阿里全集团 90%以上的文件下载任务、日分发峰值达到 1 亿次,100%成功支撑双十一营销活动数据抵达数万台机器,github Star 数已达到 2500+。2018 年 11 月 14 日已正式进入 CNCF,成为 CNCF 沙箱级别项目(Sandbox Level Project)。Dragonfly 的由来随着阿里集团业务爆炸式增长,2015 年时发布系统日均发布量突破两万,很多应用的机器规模开始破万,发布失败率开始增高,而根本原因则是发布过程需要大量的文件拉取,文件服务器扛不住大量的请求,当然第一时间会想到服务器扩容,可是扩容后又发现后端存储成为瓶颈且扩容成本也非常巨大(按照我们的计算,为了满足业务需求,不阻碍业务的发展,后续至少需要 2000 台高配物理机且上不封顶)。此外,大量来自不同 IDC 的客户端请求消耗了巨大的网络带宽,造成网络拥堵。同时,阿里巴巴很多业务走向国际化,大量的应用部署在海外,海外服务器下载要回源国内,浪费了大量的国际带宽,而且还很慢;如果传输大文件,网络环境差,失败的话又得重来一遍,效率极低。于是我们很自然的就想到了 P2P 技术,P2P 技术并不新鲜,当时也调研了很多国内外的系统,但是调研的结论是这些系统的规模和稳定性都无法达到我们的期望,因此就有了Dragonfly这个产品的诞生。Dragonfly 能解决哪些问题作为一款通用文件分发系统,Dragonfly 主要能够解决以下几个方面的问题:大规模下载问题:应用发布过程中需要下载软件包或者镜像文件,如果同时有大量机器需要发布,比如 1000台,按照 500MB 大小的镜像文件计算,如果直接从镜像仓库下载,假设镜像仓库的带宽是 10000Mbps,那么理想状态下至少需要 10 分钟,而且实际情况很可能是仓库早已被打挂。远距离传输问题:针对跨地域跨国际的应用,比如阿里速卖通,它既要在国内部署,又要在美国和俄罗斯部署,而存储软件包的源一般只在一个地域,比如国内上海,那么在美国或者俄罗斯的机器当要下载软件包的时候就要通过国际网络传输,但是国际网络不仅延时高而且极不稳定,严重影响传输效率,进而导致业务不能及时上线新功能或者问题补丁,由此甚至会产生业务故障。带宽成本问题:除了传输效率问题,高昂的带宽成本也是一个非常严重的问题,很多互联网公司尤其是视频相关的公司,带宽成本往往可以占据其总体成本的很大一部分。安全传输问题:据统计,每年因为网络安全问题导致的经济损失高达 4500 亿美元,所以安全必须是第一生命线,文件传输过程中如果不加入任何安全机制,文件内容很容易被嗅探到,假设文件中包含账号或者秘钥之类的数据,一旦被截获,后果将不堪设想。Dragonfly 是如何解决这些问题的通过 P2P 技术解决大规模镜像下载问题,原理如下:针对上图有几个概念需要先解释:PouchContainer:阿里巴巴集团开源的高效、轻量级企业级富容器引擎技术。Registry:容器镜像的存储仓库,每个镜像由多个镜像层组成,而每个镜像层又表现为一个普通文件。Block:当通过Dragonfly下载某层镜像文件时,蜻蜓的SuperNode会把整个文件拆分成一个个的块,SuperNode 中的分块称为种子块,种子块由若干初始客户端下载并迅速在所有客户端之间传播,其中分块大小通过动态计算而来。SuperNode:Dragonfly的服务端,它主要负责种子块的生命周期管理以及构造 P2P 网络并调度客户端互传指定分块。DFget__:__Dragonfly的客户端,安装在每台主机上,主要负责分块的上传与下载以及与容器 Daemon 的命令交互Peer:下载同一个文件的 Host 彼此之间称为 Peer。主要下载过程如下:首先由 Pouch Container 发起 Pull 镜像命令,该命令会被 DFget 代理截获。然后由 DFget 向 SuperNode 发送调度请求。SuperNode 在收到请求后会检查对应的文件是否已经被缓存到本地,如果没有被缓存,则会从 Registry 中下载对应的文件并生成种子块数据(种子块一旦生成就可以立即传播,而并不需要等到 SuperNode 下载完成整个文件后才开始分发),如果已经被缓存,则直接生成分块任务。客户端解析相应的任务并从其他 Peer 或者 SuperNode 中下载分块数据,当某个 Layer 的所有分块下载完成后,一个 Layer 也就下载完毕,此时会传递给容器引擎使用,而当所有的 Layer 下载完成后,整个镜像也就下载完成了。通过上述 P2P 技术,可以彻底解决镜像仓库的带宽瓶颈问题,充分利用各个 Peer 的硬件资源和网络传输能力,达到规模越大传输越快的效果。Dragonfly的系统架构不涉及对容器技术体系的任何改动,完全可以无缝支持容器使其拥有 P2P 镜像分发能力,以大幅提升文件分发效率!结合 CDN 与预热技术解决远距离传输问题通过 CDN 缓存技术,每个客户端可以就近从 SuperNode 中下载种子块,而无需跨地域进行网络传输,CDN 缓存原理大致如下:同一个文件的第一个请求者会触发检查机制,根据请求信息计算出缓存位置,如果缓存不存在,则触发回源同步操作生成种子块;否则向源站发送 HEAD 请求并带上 If-Modified-Since 字段,该字段的值为上次服务器返回的文件最后修改时间,如果响应码为 304,则表示源站中的文件目前还未被修改过,缓存文件是有效的,然后再根据缓存文件的元信息确定文件是否是完整的,如果完整,则缓存完全命中;否则需要通过断点续传方式把剩下的文件分段下载过来,断点续传的前提是源站必须支持分段下载,否则还是要同步整个文件。如果 HEAD 请求的响应码为200,则表示源站文件已被修改过,缓存无效,此时需要进行回源同步操作;如果响应码既不是 304 也不是 200,则表示源站异常或地址无效,下载任务直接失败。通过 CDN 缓存技术可以解决客户端回源下载以及就近下载的问题,但是如果缓存不命中,针对跨域远距离传输的场景,SuperNode 回源同步的效率将会非常低,这会直接影响到整体的分发效率,为了解决该问题,Dragonfly采用了一种自动化层级预热机制来最大程度的提升缓存命中率,其大致原理如下:通过 Push 命令把镜像文件推送到 Registry 的过程中,每推送完一层镜像就会立即触发 SuperNode 以 P2P 方式把该层镜像同步到 SuperNode 本地,通过这种方式,可以充分利用用户执行Push和Pull操作的时间间隙(大概10分钟左右),把镜像的各层文件同步到 SuperNode 中,这样当用户执行 Pull 命令时,就可以直接利用 SuperNode 中的缓存文件,自然而然也就没有远距离传输的问题了。通过动态压缩和智能化调度解决带宽成本问题通过动态压缩,可以在不影响 SuperNode 和 Peer 正常运行的情况下,对文件中最值得压缩的部分实施相应的压缩策略,从而可以节约大量的网络带宽资源,同时还能进一步提升分发速率,相比于传统的 HTTP 原生压缩方式,动态压缩主要有以下几个方面的优势:动态压缩的优势首先自然是动态性,它可以保证只有在 SuperNode 和 Peer 负载正常的情况下才会开启压缩,同时只会对文件中最值得压缩的分块进行压缩且压缩策略也是动态确定的;此外,通过多线程压缩方式可以大幅提升压缩速率,而且借助 SuperNode 的缓存能力,整个下载过程只需要压缩一次即可,压缩收益比相对于 HTTP 原生方式至少提升 10 倍。除了动态压缩外,通过 SuperNode 强大的任务调度能力,可以尽量使在同一个网络设备下的 Peer 互传分块,减少跨网络设备、跨机房的流量,从而进一步降低网络带宽成本。通过加密插件解决安全传输问题在下载某些敏感类文件(比如秘钥文件或者账号数据之类的文件)时,传输的安全性必须要得到有效保障,在这方面,Dragonfly主要做了以下几个方面的工作:支持 HTTP Header 传输,以满足那些需要通过 Header 来进行权限验证的下载请求通过自研的数据存储协议对数据块进行包装传输,后续还会对包装的数据进行再加密即将支持安全加密功能插件化通过多重校验机制,可以严格防止数据被篡改Dragonfly目前的成熟度如何在阿里巴巴集团内部,Dragonfly作为全集团基础技术构件,目前已经承载了全集团 90%以上的文件下载任务,包括镜像文件、应用软件包、算法数据文件、静态资源文件以及索引文件等等,日分发峰值目前可以达到 1 亿次,为集团业务提供了高效稳定的文件分发能力;同时,每年双十一大家买买买的过程中,其中最为关键的营销活动数据(数 GB 大小)也是在将近零点的时候通过Dragonfly来成功(100%成功)抵达数万台机器上的,万一在这个过程中有一点点问题,双十一会如何,你懂的……目前 Dragonfly 也已经开源,在开源社区中, 目前 Star 数 2500+,同时有非常多的外部用户对 Dragonfly 表现出浓厚的兴趣,也有很多外部公司正在使用 Dragonfly 来解决他们在镜像或者文件分发方面遇到的各种问题,比如中国移动、滴滴、科大讯飞等;此外,Dragonfly 已成为全中国第三个进入CNCF Sandbox 级别的项目,后续我们还会继续加油努力,争取尽快毕业!通过以上介绍,我相信针对Dragonfly是否足够成熟,大家心里应该也有杆秤了吧,当然,Dragonfly还有很多事情需要不断完善和改进,在这里诚邀各路人才,一起把Dragonfly打造成一款世界级的产品!未来规则展望成为CNCF毕业项目,为云原生应用提供更加丰富和强大的文件分发能力。开源版与集团内部版融合,给社区开放出更多的高级特性。智能化方面进行更多探索和改进。本文作者:amber涂南阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

November 20, 2018 · 1 min · jiezi

美团容器平台架构及容器技术实践

本文根据美团基础架构部/容器研发中心技术总监欧阳坚在2018 QCon(全球软件开发大会)上的演讲内容整理而成。背景美团的容器集群管理平台叫做HULK。漫威动画里的HULK在发怒时会变成“绿巨人”,它的这个特性和容器的“弹性伸缩”很像,所以我们给这个平台起名为HULK。貌似有一些公司的容器平台也叫这个名字,纯属巧合。2016年,美团开始使用容器,当时美团已经具备一定的规模,在使用容器之前就已经存在的各种系统,包括CMDB、服务治理、监控告警、发布平台等等。我们在探索容器技术时,很难放弃原有的资产。所以容器化的第一步,就是打通容器的生命周期和这些平台的交互,例如容器的申请/创建、删除/释放、发布、迁移等等。然后我们又验证了容器的可行性,证实容器可以作为线上核心业务的运行环境。2018年,经过两年的运营和实践探索,我们对容器平台进行了一次升级,这就是容器集群管理平台HULK 2.0。把基于OpenStack的调度系统升级成容器编排领域的事实标准Kubernetes(以后简称K8s)。提供了更丰富可靠的容器弹性策略。针对之前在基础系统上碰到的一些问题,进行了优化和打磨。美团的容器使用状况是:目前线上业务已经超过3000个服务,容器实例数超过30000个,很多大并发、低延时要求的核心链路服务,已经稳定地运行在HULK之上。本文主要介绍我们在容器技术上的一些实践,属于基础系统优化和打磨。美团容器平台的基本架构首先介绍一下美团容器平台的基础架构,相信各家的容器平台架构大体都差不多。首先,容器平台对外对接服务治理、发布平台、CMDB、监控告警等等系统。通过和这些系统打通,容器实现了和虚拟机基本一致的使用体验。研发人员在使用容器时,可以和使用VM一样,不需要改变原来的使用习惯。此外,容器提供弹性扩容能力,能根据一定的弹性策略动态增加和减少服务的容器节点数,从而动态地调整服务处理能力。这里还有个特殊的模块——“服务画像”,它的主要功能是通过对服务容器实例运行指标的搜集和统计,更好的完成调度容器、优化资源分配。比如可以根据某服务的容器实例的CPU、内存、IO等使用情况,来分辨这个服务属于计算密集型还是IO密集型服务,在调度时尽量把互补的容器放在一起。再比如,我们可以知道某个服务的每个容器实例在运行时会有大概500个进程,我们就会在创建容器时,给该容器加上一个合理的进程数限制(比如最大1000个进程),从而避免容器在出现问题时,占用过多的系统资源。如果这个服务的容器在运行时,突然申请创建20000个进程,我们有理由相信是业务容器遇到了Bug,通过之前的资源约束对容器进行限制,并发出告警,通知业务及时进行处理。往下一层是“容器编排”和“镜像管理”。容器编排解决容器动态实例的问题,包括容器何时被创建、创建到哪个位置、何时被删除等等。镜像管理解决容器静态实例的问题,包括容器镜像应该如何构建、如何分发、分发的位置等等。最下层是我们的容器运行时,美团使用主流的Linux+Docker容器方案,HULK Agent是我们在服务器上的管理代理程序。把前面的“容器运行时”具体展开,可以看到这张架构图,按照从下到上的顺序介绍:最下层是CPU、内存、磁盘、网络这些基础物理资源。往上一层,我们使用的是CentOS7作为宿主机操作系统,Linux内核的版本是3.10。我们在CentOS发行版默认内核的基础上,加入一些美团为容器场景研发的新特性,同时为高并发、低延时的服务型业务做了一些内核参数的优化。再往上一层,我们使用的是CentOS发行版里自带的Docker,当前的版本是1.13,同样,加入了一些我们自己的特性和增强。HULK Agent是我们自己开发的主机管理Agent,在宿主机上管理Agent。Falcon Agent同时存在于宿主机和容器内部,它的作用是收集宿主机和容器的各种基础监控指标,上报给后台和监控平台。最上一层是容器本身。我们现在主要支持CentOS 6和CentOS 7两种容器。在CentOS 6中有一个container init进程,它是我们开发容器内部的1号进程,作用是初始化容器和拉起业务进程。在CentOS 7中,我们使用了系统自带的systemd作为容器中的1号进程。我们的容器支持各种主流编程语言,包括Java、Python、Node.js、C/C++等等。在语言层之上是各种代理服务,包括服务治理的Agent、日志Agent、加密Agent等等。同时,我们的容器也支持美团内部的一些业务环境,例如set信息、泳道信息等,配合服务治理体系,可以实现服务调用的智能路由。美团主要使用了CentOS系列的开源组件,因为我们认为Red Hat有很强的开源技术实力,比起直接使用开源社区的版本,我们希望Red Hat的开源版本能够帮助解决大部分的系统问题。我们也发现,即使部署了CentOS的开源组件,仍然有可能会碰到社区和Red Hat没有解决的问题。从某种程度上也说明,国内大型互联公司在技术应用的场景、规模、复杂度层面已经达到了世界领先的水平,所以才会先于社区、先于Red Hat的客户遇到这些问题。容器遇到的一些问题在容器技术本身,我们主要遇到了4个问题:隔离、稳定性、性能和推广。隔离包含两个层面:第一个问题是,容器能不能正确认识自身资源配置;第二个问题是,运行在同一台服务器上的容器会不会互相影响。比如某一台容器的IO很高,就会导致同主机上的其他容器服务延时增加。稳定性:这是指在高压力、大规模、长时间运行以后,系统功能可能会出现不稳定的问题,比如容器无法创建、删除,因为软件问题发生卡死、宕机等问题。性能:在虚拟化技术和容器技术比较时,大家普遍都认为容器的执行效率会更高,但是在实践中,我们遇到了一些特例:同样的代码在同样配置的容器上,服务的吞吐量、响应时延反而不如虚拟机。推广:当我们把前面几个问题基本上都解决以后,仍然可能会碰到业务不愿意使用容器的情况,其中原因一部分是技术因素,例如容器接入难易程度、周边工具、生态等都会影响使用容器的成本。推广也不是一个纯技术问题,跟公司内部的业务发展阶段、技术文化、组织设置和KPI等因素都密切相关。容器的实现容器本质上是把系统中为同一个业务目标服务的相关进程合成一组,放在一个叫做namespace的空间中,同一个namespace中的进程能够互相通信,但看不见其他namespace中的进程。每个namespace可以拥有自己独立的主机名、进程ID系统、IPC、网络、文件系统、用户等等资源。在某种程度上,实现了一个简单的虚拟:让一个主机上可以同时运行多个互不感知的系统。此外,为了限制namespace对物理资源的使用,对进程能使用的CPU、内存等资源需要做一定的限制。这就是Cgroup技术,Cgroup是Control group的意思。比如我们常说的4c4g的容器,实际上是限制这个容器namespace中所用的进程,最多能够使用4核的计算资源和4GB的内存。简而言之,Linux内核提供namespace完成隔离,Cgroup完成资源限制。namespace+Cgroup构成了容器的底层技术(rootfs是容器文件系统层技术)。美团的解法、改进和优化隔离之前一直和虚拟机打交道,但直到用上容器,才发现在容器里面看到的CPU、Memory的信息都是服务器主机的信息,而不是容器自身的配置信息。直到现在,社区版的容器还是这样,比如一个4c4g的容器,在容器内部可以看到有40颗CPU、196GB内存的资源,这些资源其实是容器所在宿主机的信息。这给人的感觉,就像是容器的“自我膨胀”,觉得自己能力很强,但实际上并没有,还会带来很多问题。上图是一个内存信息隔离的例子。获取系统内存信息时,社区Linux无论在主机上还是在容器中,内核都是统一返回主机的内存信息,如果容器内的应用,按照它发现的宿主机内存来进行配置的话,实际资源是远远不够的,导致的结果就是:系统很快会发生OOM异常。我们做的隔离工作,是在容器中获取内存信息时,内核根据容器的Cgroup信息,返回容器的内存信息(类似LXCFS的工作)。CPU信息隔离的实现和内存的类似,不再赘述,这里举一个CPU数目影响应用性能例子。大家都知道,JVM GC(垃圾对象回收)对Java程序执行性能有一定的影响。默认的JVM使用公式“ParallelGCThreads = (ncpus <= 8) ? ncpus : 3 + ((ncpus * 5) / 8)” 来计算做并行GC的线程数,其中ncpus是JVM发现的系统CPU个数。一旦容器中JVM发现了宿主机的CPU个数(通常比容器实际CPU限制多很多),这就会导致JVM启动过多的GC线程,直接的结果就导致GC性能下降。Java服务的感受就是延时增加,TP监控曲线突刺增加,吞吐量下降。针对这个问题有各种解法:显式的传递JVM启动参数“-XX:ParallelGCThreads”告诉JVM应该启动几个并行GC线程。它的缺点是需要业务感知,为不同配置的容器传不同的JVM参数。在容器内使用Hack过的glibc,使JVM(通过sysconf系统调用)能正确获取容器的CPU资源数。我们在一段时间内使用的就是这种方法。其优点是业务不需要感知,并且能自动适配不同配置的容器。缺点是必须使用改过的glibc,有一定的升级维护成本,如果使用的镜像是原生的glibc,问题也仍然存在。我们在新平台上通过对内核的改进,实现了容器中能获取正确CPU资源数,做到了对业务、镜像和编程语言都透明(类似问题也可能影响OpenMP、Node.js等应用的性能)。有一段时间,我们的容器是使用root权限进行运行,实现的方法是在docker run的时候加入‘privileged=true’参数。这种粗放的使用方式,使容器能够看到所在服务器上所有容器的磁盘,导致了安全问题和性能问题。安全问题很好理解,为什么会导致性能问题呢?可以试想一下,每个容器都做一次磁盘状态扫描的场景。当然,权限过大的问题还体现在可以随意进行mount操作,可以随意的修改NTP时间等等。在新版本中,我们去掉了容器的root权限,发现有一些副作用,比如导致一些系统调用失败。我们默认给容器额外增加了sys_ptrace和sys_admin两个权限,让容器可以运行GDB和更改主机名。如果有特例容器需要更多的权限,可以在我们的平台上按服务粒度进行配置。Linux有两种IO:Direct IO和Buffered IO。Direct IO直接写磁盘,Buffered IO会先写到缓存再写磁盘,大部分场景下都是Buffered IO。我们使用的Linux内核3.X,社区版本中所有容器Buffer IO共享一个内核缓存,并且缓存不隔离,没有速率限制,导致高IO容器很容易影响同主机上的其他容器。Buffer IO缓存隔离和限速在Linux 4.X里通过Cgroup V2实现,有了明显的改进,我们还借鉴了Cgroup V2的思想,在我们的Linux 3.10内核实现了相同的功能:每个容器根据自己的内存配置有对应比例的IO Cache,Cache的数据写到磁盘的速率受容器Cgroup IO配置的限制。Docker本身支持较多对容器的Cgroup资源限制,但是K8s调用Docker时可以传递的参数较少,为了降低容器间的互相影响,我们基于服务画像的资源分配,对不同服务的容器设定不同的资源限制,除了常见的CPU、内存外,还有IO的限制、ulimit限制、PID限制等等。所以我们扩展了K8s来完成这些工作。业务在使用容器的过程中产生core dump文件是常见的事,比如C/C++程序内存访问越界,或者系统OOM的时候,系统选择占用内存多的进程杀死,默认都会生成一个core dump文件。社区容器系统默认的core dump文件会生成在宿主机上,由于一些core dump文件比较大,比如JVM的core dump通常是几个GB,或者有些存在Bug的程序,其频发的core dump很容易快速写满宿主机的存储,并且会导致高磁盘IO,也会影响到其他容器。还有一个问题是:业务容器的使用者没有权限访问宿主机,从而拿不到dump文件进行下一步的分析。为此,我们对core dump的流程进行了修改,让dump文件写到容器自身的文件系统中,并且使用容器自己的Cgroup IO吞吐限制。稳定性我们在实践中发现,影响系统稳定性的主要是Linux Kernel和Docker。虽然它们本身是很可靠的系统软件,但是在大规模、高强度的场景中,还是会存在一些Bug。这也从侧面说明,我们国内互联网公司在应用规模和应用复杂度层面也属于全球领先。在内核方面,美团发现了Kernel 4.x Buffer IO限制的实现问题,得到了社区的确认和修复。我们还跟进了一系列CentOS的Ext4补丁,解决了一段时间内进程频繁卡死的问题。我们碰到了两个比较关键的Red Hat版Docker稳定性问题:在Docker服务重启以后,Docker exec无法进入容器,这个问题比较复杂。在解决之前我们用nsenter来代替Docker exec并积极反馈给RedHat。后来Red Hat在今年初的一个更新解决了这个问题。https://access.redhat.com/errata/RHBA-2017:1620是在特定条件下Docker Daemon会Panic,导致容器无法删除。经过我们自己Debug,并对比最新的代码,发现问题已经在Docker upstream中得到解决,反馈给Red Hat也很快得到了解决。https://github.com/projectatomic/containerd/issues/2面对系统内核、Docker、K8s这些开源社区的系统软件,存在一种观点是:我们不需要自己分析问题,只需要拿社区的最新更新就行了。但是我们并不认同,我们认为技术团队自身的能力很重要,主要是如下原因:美团的应用规模大、场景复杂,很多问题也许很多企业都没有遇到过,不能被动的等别人来解答。对于一些实际的业务问题或者需求(例如容器内正确返回CPU数目),社区也许觉得不重要,或者不是正确的理念,可能就不会解决。社区很多时候只在Upstream解决问题,而Upstream通常不稳定,即使有Backport到我们正在使用的版本,排期也很难进行保障。社区会发布很多补丁,通常描述都比较晦涩难懂。如果没有对问题的深刻理解,很难把遇到的实际问题和一系列补丁联系起来。对于一些复杂问题,社区的解决方案不一定适用于我们自身的实际场景,我们需要自身有能力进行判断和取舍。美团在解决开源系统问题时,一般会经历五个阶段:自己深挖、研发解决、关注社区、和社区交互,最后贡献给社区。性能容器平台性能,主要包括两个方面性能:业务服务运行在容器上的性能。容器操作(创建、删除等等)的性能。上图是我们CPU分配的一个例子,我们采用的主流服务器是两路24核服务器,包含两个Node,每个12核,算上超线程共48颗逻辑CPU。属于典型的NUMA(非一致访存)架构:系统中每个Node有自己的内存,Node内的CPU访问自己的内存的速度,比访问另一个Node内存的速度快很多(差一倍左右)。过去我们曾经遇到过网络中断集中到CPU0上的问题,在大流量下可能导致网络延时增加甚至丢包。为了保证网络处理能力,我们从Node0上划出了8颗逻辑CPU用来专门处理网络中断和宿主机系统上的任务,例如镜像解压这类高CPU的工作,这8颗逻辑CPU不运行任何容器的Workload。在容器调度方面,我们的容器CPU分配尽量不跨Node,实践证明跨Node访问内存对应用性能的影响比较大。在一些计算密集型的场景下,容器分配在Node内部会提升30%以上的吞吐量。按Node的分配方案也存在一定的弊端:会导致CPU的碎片增加,为了更高效地利用CPU资源。在实际系统中,我们会根据服务画像的信息,分配一些对CPU不敏感的服务容器跨Node使用CPU资源。上图是一个真实的服务在CPU分配优化前后,响应延时的TP指标线对比。可以看到TP999线下降了一个数量级,所有的指标都更加平稳。性能优化:文件系统针对文件系统的性能优化,第一步是选型,根据统计到的应用读写特征,我们选择了Ext4文件系统(超过85%的文件读写是对小于1M文件的操作)。Ext4文件系统有三种日志模式:Journal:写数据前等待Metadata和数据的日志落盘。Ordered:只记录Metadata的日志,写Metadata日志前确保数据已经落盘。Writeback:仅记录Metadata日志,不保证数据比Metadata先落盘。我们选择了Writeback模式(默认是oderded),它在几种挂载模式中速度最快,缺点是:发生故障时数据不好恢复。我们大部分容器处于无状态,故障时在别的机器上再拉起一台即可。因此我们在性能和稳定性中,选择了性能。容器内部给应用提供可选的基于内存的文件系统tmpfs,可以提升有大量临时文件读写的服务性能。如上图所示,在美团内部创建一个虚拟机至少经历三步,平均时间超过300秒。使用镜像创建容器平均时间23秒。容器的灵活、快速得到了显著的体现。容器扩容23秒的平均时间包含了各个部分的优化,如扩容链路优化、镜像分发优化、初始化和业务拉起优化等等。接下来,本文主要介绍一下我们做的镜像分发和解压相关的优化。上图是美团容器镜像管理的总体架构,其特点如下:存在多个Site。支持跨Site的镜像同步,根据镜像的标签确定是否需要跨Site同步。每个Site有镜像备份。每个Site内部有实现镜像分发的P2P网络。镜像分发是影响容器扩容时长的一个重要环节。跨Site同步:保证服务器总能从就近的镜像仓库拉取到扩容用的镜像,减少拉取时间,降低跨Site带宽消耗。基础镜像预分发:美团的基础镜像是构建业务镜像的公共镜像,通常有几百兆的大小。业务镜像层是业务的应用代码,通常比基础镜像小很多。在容器扩容的时候如果基础镜像已经在本地,就只需要拉取业务镜像的部分,可以明显的加快扩容速度。为达到这样的效果,我们会把基础镜像事先分发到所有的服务器上。P2P镜像分发:基础镜像预分发在有些场景会导致上千个服务器同时从镜像仓库拉取镜像,对镜像仓库服务和带宽带来很大的压力。因此我们开发了镜像P2P分发的功能,服务器不仅能从镜像仓库中拉取镜像,还能从其他服务器上获取镜像的分片。从上图可以看出,随着分发服务器数目的增加,原有分发时间也快速增加,而P2P镜像分发时间基本上保持稳定。Docker的镜像拉取是一个并行下载,串行解压的过程,为了提升解压的速度,我们美团也做了一些优化工作。对于单个层的解压,我们使用并行解压算法替换Docker默认的串行解压算法,实现上是使用pgzip替换gzip。Docker的镜像具有分层结构,对镜像层的合并是一个“解压一层合并一层,再解压一层,再合并一层”的串行操作。实际上只有合并是需要串行的,解压可以并行起来。我们把多层的解压改成并行,解压出的数据先放在临时存储空间,最后根据层之间的依赖进行串行合并。前面的改动(并行解压所有的层到临时空间)导致磁盘IO的次数增加了近一倍,也会导致解压过程不够快。于是,我们使用基于内存的Ramdisk来存储解压出来的临时文件,减轻了额外文件写带来的开销。做了上面这些工作以后,我们又发现,容器的分层也会影响下载加解压的时间。上图是我们简单测试的结果:无论对于怎么分层的镜像并行解压,都能大幅提升解压时间,对于层数多的镜像提升更加明显。推广推广容器的第一步是能说出容器的优势,我们认为容器有如下优势:轻量级:容器小、快,能够实现秒级启动。应用分发:容器使用镜像分发,开发测试容器和部署容器配置完全一致。弹性:可以根据CPU、内存等资源使用或者QPS、延时等业务指标快速扩容容器,提升服务能力。这三个特性的组合,可以给业务带来更大的灵活度和更低的计算成本。因为容器平台本身是一个技术产品,它的客户是各个业务的RD团队,因此我们需要考虑下面一些因素:产品优势:推广容器平台从某种程度上讲,自身是一个ToB的业务,首先要有好的产品,它相对于以前的解决方案(虚拟机)存在很多优势。和已有系统打通:这个产品要能和客户现有的系统很好的进行集成,而不是让客户推翻所有的系统重新再来。原生应用的开发平台、工具:这个产品要易于使用,要有配合工作的工具链。虚拟机到容器的平滑迁移:最好能提供从原有方案到新产品的迁移方案,并且容易实施。与应用RD紧密配合:要提供良好的客户支持,(即使有些问题不是这个产品导致的也要积极帮忙解决)。资源倾斜:从战略层面支持颠覆性新技术:资源上向容器平台倾斜,没有足够的理由,尽量不给配置虚拟机资源。总结Docker容器加Kubernetes编排是当前容器云的主流实践之一,美团容器集群管理平台HULK也采用了这样的方案。本文主要分享了美团在容器技术上做的一些探索和实践。内容主要涵盖美团容器云在Linux Kernel、Docker和Kubernetes层面做的一些优化工作,以及美团内部推动容器化进程的一些思考,欢迎大家跟我们交流、探讨。作者简介欧阳坚,2006年毕业于清华大学计算机系,拥有12年数据中心开发管理经验。曾任VMware中国Staff Engineer,无双科技CTO,中科睿光首席架构师。现任美团基础架构部/容器研发中心技术总监,负责美团容器化的相关工作。招聘信息美团点评基础架构团队诚招Java高级、资深技术专家,Base北京、上海。我们是集团致力于研发公司级、业界领先基础架构组件的核心团队,涵盖分布式监控、服务治理、高性能通信、消息中间件、基础存储、容器化、集群调度等技术领域。欢迎有兴趣的同学投送简历到 liuxing14@meituan.com。

November 16, 2018 · 1 min · jiezi

有赞容器化实践

前言容器化已经成为一种趋势,它可以解决很多运维中的痛点,比如效率、成本、稳定性等问题,而接入容器的过程中往往也会碰到很多问题和不便。在有赞最开始做容器化是为了快速交付开发测试环境,在容器化的过程中,我们碰到过容器技术、运维体系适配、用户使用习惯改变等各种问题,本文主要介绍有赞容器化过程中碰到的问题以及采取的方案。有赞容器化的初衷在有赞同时会有很多个项目、日常在并行开发,环境的抢占问题严重影响了开发、测试和上线的效率,我们需要给每个项目提供一套开发联调(daily)、测试环境(qa),并且随着项目、日常的生命周期项目环境也会随着创建和销毁,我们最早的容器化需求就是怎么解决环境快速交付的问题。[有赞环境]上面是有赞大致的研发流程,在标准流程中我们有四套稳定环境,分别是 Daily 环境、Qa 环境、预发环境和测试环境。我们的开发、测试、联调工作一般并不会直接在稳定环境中进行,而是会拉一套独立的项目环境出来,随着代码经过开发、测试、预发验收最终发布到生产环境后再同步回 Daily/Qa 的稳定环境中。[项目环境]我们提供了一套以最小的资源投入满足最大项目并行度的环境交付方案,在 Daily/Qa 稳定环境的基础上,隔离出N个项目环境,在项目环境里只需要创建该项目所涉及应用的计算资源,其它缺失的服务调用由稳定环境提供,在项目环境里,我们大量使用了容器技术。[持续交付]后面我们又在项目环境快速交付的解决方案的基础上实现了持续交付流水线,目前已经有超过 600 套项目/持续交付环境,加上 Daily/Qa 稳定环境,涉及计算实例四五千个,这些计算实例无论是 cpu 还是内存使用率都是非常低的,容器化可以非常好的解决环境交付的效率问题,以及提高资源使用率来节省成本的投入。有赞容器化方案我们的容器化方案基于 kubernetes(1.7.10)和 docker(1.12.6)、docker(1.13.1),下面介绍一下我们在各个方面遇到的问题以及解决方案。网络有赞后端主要是 java 应用,采用定制的 dubbo 服务化方案,过程中无法做到整个单元全量容器化,和原有集群在网络路由上互通也就成了刚需,由于我们无法解决公有云上 overlay 网络和公有云网络的互通问题,所以一开始我们放弃了 overlay 网络方案,采用了托管网络下的 macvlan 方案,这样既解决了网络互通的问题也不存在网络性能问题,但是也就享受不到公有云弹性资源的优势了。随着有赞多云架构的发展以及越来越多的云厂商支持容器 overlay 网络和 vpc 网络打通,弹性资源的问题才得到了缓解。隔离性容器的隔离主要利用内核的 namespace 和 cgroup 技术,在进程、cpu、内存、IO等资源隔离限制上有比较好的表现,但其他方面和虚拟机相比存在着很多的不足,我们在使用过程中碰到最多的问题是容器里看到的 cpu 数和内存大小不准确,因为/proc文件系统无法隔离,导致容器里的进程"看到"的是物理机的 cpu 数以及内存大小。内存问题我们的 java 应用会根据服务器的内存大小来决定 jvm 参数应该怎么配置,我们是采用 lxcfs 方案来规避的。CPU 数的问题因为我们有超卖的需求以及 kubernetes 默认也是采用 cpu share 来做 cpu 限制,虽然我们使用了 lxcfs,CPU 数还是不准的。jvm 以及很多 Java sdk 都会根据系统的 CPU 数来决定创建多少线程,导致 java 应用在线程数和内存使用上都比虚拟机多的多,严重影响运行,其他类型的应用也有类似的问题。我们会根据容器的规格内置一个环境变量 NUM_CPUS,然后比如 nodejs 应用就会按照这个变量来创建它的 worker 进程数。在解决 java 类应用的问题时,我们索性通过 LD_PRELOAD 将 JVM_ActiveProcessorCount 函数覆盖掉,让它直接返回 NUM_CPUS 的值[1]。应用接入在容器化之前,有赞的应用已经全部接入到发布系统,在发布系统里已经标准化了应用的打包、发布流程,所以在应用接入方面成本还是比较小的,业务方无需提供 Dockerfile。nodejs, python,php-soa 等用 supervisord 托管的应用,只需要在 git 仓库里提供 app.yaml 文件定义运行需要的 runtime 和启动命令即可。java 标准化启动的应用业务方无需改动java 非标准化的应用需要做标准化改造镜像集成容器镜像我们分了三层,依次为 stack 层(os),runtime 层(语言环境),应用层(业务代码和一些辅助agent),应用以及辅助 agent 由 runit 来启动。由于我们的配置还没有完全分离,在应用层目前还是每个环境独立打包,镜像里除了业务代码之外,我们还会根据业务的语言类型放一些辅助的 agent。我们一开始也想将各种 agent 拆成多个镜像,然后每个 pod 运行多个容器,后来因为解决不了 pod 里容器的启动顺序(服务启动有依赖)问题,就把所有服务都扔到一个容器里去运行了。我们的容器镜像集成过程也是通过 kubernetes 来调度的(会调度到指定的打包节点上),在发布任务发起时,管控系统会在集群中创建一个打包的 pod,打包程序会根据应用类型等参数编译代码、安装依赖,并且生成 Dockerifile,然后在这个 pod 中使用 docker in docker 的方式来集成容器镜像并推送到仓库。为了加速应用的打包速度,我们用 pvc 缓存了 python 的 virtualenv,nodejs 的 node_modules,java 的 maven 包等文件。另外就是 docker 早的版本里,Dockerfile ADD 指令是不支持指定文件属主和分组的,这样会带来一个问题就是需要指定文件属主时(我们的应用是以 app 账号运行的)需要多运行一次 RUN chown,这样镜像也就多了一层数据,所以我们打包节点的 docker 版本采用了官方比较新的 ce 版本,因为新版本支持 ADD –chown 特性。负载均衡(ingress)有赞的应用内部调用有比较完善的服务化和 service mesh 方案,集群内的访问不用过多考虑,负载均衡只需要考虑用户和系统访问的 http 流量,在容器化之前我们已经自研了一套统一接入系统,所以在容器化负载均衡上我们并没有完整按照 ingress 的机制来实现 controller,ingress 的资源配置是配在统一接入里的,配置里面转发的 upstream 会和 kubernetes 里的 service 关联,我们只是做了一个 sync 程序 watch kube-api,感知 service 的变化来实时更新统一接入系统中 upstream 的服务器列表信息。容器登录和调试在容器化接入过程中开发会反馈是控制台比较难用,虽然我们优化了多次,和 iterm2 等的体验还是有所不足,最终我们还是放开了项目/持续交付环境这种需要频繁登陆调试的 ssh 登陆权限。另外一个比较严重的问题是,当一个应用启动后健康检查有问题会导致 pod 一直在重新调度,而在开发过程中开发肯定是希望看到失败现场的,我们提供了调试发布模式,让容器不做健康检查。日志有赞有专门的日志系统,我们内部叫天网,大部分日志以及业务监控数据都是通过 sdk 直接打到天网里去了,所以容器的标准输出日志仅仅作为一种辅助排查问题的手段。我们容器的日志收集采用的是 fluentd,经过 fluentd 处理后按照天网约定的日志格式打到 kafka,最终由天网处理进入 es 做存储。灰度发布我们涉及到灰度发布的流量主要包含三部分:用户端的 http 访问流量应用之间的 http 调用应用之间的 dubbo 调用首先,我们在入口的统一接入上统一打上灰度需要用的各种维度的标签(比如用户、店铺等),然后需要对统一接入、http client 以及 dubbo client 做改造,目的是让这些标签能够在整个调用链上透传。我们在做容器灰度发布时,会发一个灰度的 deployment,然后在统一接入以及灰度配置中心配置灰度规则,整个链路上的调用方都会感知这些灰度规则来实现灰度发布。标准环境容器化标准环境的出发点和项目环境类似,标准稳定环境中的 daily,qa,pre 以及 prod 中超过一半运行在低水位的服务器的资源非常浪费。因为成本考虑 daily,qa,pre 里都是以单台虚拟机运行的,这样一旦需要发布稳定环境将会造成标准稳定环境和项目环境的短暂不可用。虚拟机交付速度比较慢,使用虚拟机做灰度发布也比较复杂。虚拟机往往会存在几年甚至更长的时间,运行过程中操作系统以及基础软件版本的收敛非常麻烦。标准环境容器化推进经过之前项目/持续交付的上线和迭代,大部分应用本身已经具备了容器化的条件。不过对于上线来说,需要整个运维体系来适配容器化,比如监控、发布、日志等等。目前我们生产环境容器化准备基本完成,生产网已经上了部分前端 nodejs 应用,其他应用也在陆续推动中,希望以后可以分享更多生产环境中的容器化经验。结束语以上是有赞在容器化上的应用,以及在容器化过程中碰到的一些问题和解决方案,我们生产环境的容器化还处于开始阶段,后面还会碰到各种个样的问题,希望能够和大家互相学习,后面能够有更多的经验分享给大家。参考文献[1] https://github.com/fabianenar… ...

September 29, 2018 · 1 min · jiezi