centos下安装docker以及docker-composer

背景docker已经出来了很久,而我一直想混迹到docker大军中进行冲锋陷阵,恰逢公司项目的需要,因此今天玩了一把docker的安装事先准备centos系统或者linux系统安装步骤安装基础的工具yum-utils device-mapper-persistent-data lvm2yum install -y yum-utils device-mapper-persistent-data lvm2 添加docker-ce yum仓库因为在阿里云的ecs机器上面,yum镜像使用的阿里云的,找不到docker的镜像,所以需要第一步的安装基础工具之后使用yum-config-manager来添加对应的yum仓库,执行如下命令yum-config-manager –add-repo https://download.docker.com/linux/centos/docker-ce.repo 安装docker-ce通过上面两个步骤我们已经建立好了docker-ce的镜像链接,接下来只需要安装docker-ce就可以了,执行如下命令,安装这个的时间会稍微有点长,需要耐心的等待大概3-5分钟yum install docker-ce 启动、停止docker服务systemctl start docker #启动docker服务systemctl stop docker #关闭docker服务安装docker-compose安装docker-compose相对比较简单,可以直接去https://github.com/docker/com… 下载然后选择相应的版本,或者直接执行如下命令安装,安装完后docker-compose会被安装到/usr/local/bin目录下 curl -L https://github.com/docker/compose/releases/download/1.24.0-rc1/docker-compose-`uname -s-uname -m` -o /usr/local/bin/docker-compose设置docker-compose可执行sudo chmod +x /usr/local/bin/docker-compose 查看docker-compose是否安装成功docker-compose –version 至此,安装过程已经全部完成,第一次入坑docker已经完成!

January 16, 2019 · 1 min · jiezi

如何利用数据架构带动企业增长?

对于架构师而言,技术的发展是无尽的,在搭建和实践智能数据架构的过程中,架构师们都会或多或少地遇到一些疑惑和挑战,如何解决在架构建设中遇到的某些问题?架构建设的领域又有什么新的行业动态和技术方法?近日,在个推TechDay全国沙龙北京站的现场,几位资深架构师围绕“以智能数据架构,挖掘增长金矿”的主题,开启了对智能数据技术的深入探讨。贝壳金服2B2C CTO史海峰 《架构之十年磨剑,大巧不工》在传统的IT行业中,企业级业务系统是技术水平的高峰,比如电信、金融、税务系统。以电信行业为例,它有其独有的行业特征:业务系统本身就是生产系统,信息化程度高;24小时全天候跨地域不间断地提供服务;业务复杂,功能多样等。电信行业中以中国移动为代表的架构设计规划最为领先,该架构有一套完整的设计规范,整体建设周期也很长。而在PC互联网时代,电子商务纷纷崛起,很多互联网电商平台都面临着从自营到平台化转变的挑战。自营与平台化最大的区别在于,自营只有一个商户,而真正的平台则需要像云计算一样,支持多商户的入驻与经营。对于架构师来说,要实现这样的平台转型,需要有清晰的业务系统架构总图、系统架构蓝图以及技术架构规划。进入到移动互联网时代,手机的普及使得O2O快速发展。O2O平台对于系统稳定性的要求非常高,而且对业务发展的响应速度也提出了新的 ,其所需要的技术架构非常复杂、需要有严谨的基础架构和运维机制,还要维持创新能力。到了产业互联网时代,架构师应该对行业有更深入的理解和实践,同时,也要有相应的架构思维,从“点、线、面、体”各维度提升认知,在进行架构建设时,架构师不仅要考虑到技术上实现的可能性,也要考虑到行业特点,以及企业各个方面对于互联网技术的态度与需求。个推平台架构主管王志豪 《微服务网关架构实践》微服务是指,将单一的应用程序拆分成多个微小的服务,各个小服务之间低耦合,高内聚,每个小的服务可以单独进行开发,不依赖于具体的编程语言,也可以使用不同的数据存储技术,各个服务可以独立部署,拥有各自的进程,相互之间通过轻量化的机制进行通信,所有的服务共同实现具体的业务功能。个推整体的微服务架构,主要是基于Docker和Kubernetes进行实践的。个推将应用服务分为三层,最上层是API网关,为服务提供统一的入口;第二层是业务逻辑层,主要实现具体的业务逻辑;最底层是基础服务层,为同一产品线下的不同产品提供共同的基础服务。在构建整个微服务体系时,个推选择了研发自己的网关。目前市面也有很多优秀的网关产品,但都并不是特别适合个推的微服务体系。第一,个推的配置和服务注册与发现均基于Consul实现的;第二,很多的开源的网关的配置相对于个推的需求略显复杂;第三,由于不同的功能很难用统一的网关去实现,个推更希望在API网关的功能扩展上保持足够的灵活性;第四,个推的微服务体系是基于Docker和Kubernetes进行实践的,其他的网关较难直接融入个推的微服务体系。个推选取了OpenResty+Lua作为实现网关的技术选型。其中OpenResty是一个基于 Nginx 与 Lua 的高性能 Web 平台,Lua则是一个较为轻量的、扩展性较强的语言。同时,个推也借鉴了Kong和Orange的插件机制,通过插件的方式实现网关功能的扩展。个推的微服务网关的设计重点,体现在插件的设计和请求过程,每个插件都会在OpenResty的一个或多个阶段起作用。在进行请求处理的时候,个推会按照产品配置和插件规则筛选出具体执行的插件,然后实例化插件,对流量进行处理。个推微服务网关还有规则配置简单,不同插件实现灵活,配置基于Conusl实时热更新等特点。在自己的微服务网关中,个推还实现了诸如动态路由、流量控制、Auth鉴权、链路追踪、A/B Testing等功能。在实践微服务网关的过程中,个推还有一些有待改进的地方,比如:网关的弹性设计不够、网关还需要进一步与DevOps进行结合等。百度主任架构师郑然《搜索引擎的大数据计算架构》搜索引擎的建设主要分为三个方面,第一,是内容抓取部分,爬虫会将海量信息抓取下来;第二,是检索系统部分,系统需要加入对已抓取信息的内容的理解,放到索引中;第三,是索引构建部分。支持搜索引擎计算的建库部分中,有一个非常核心的系统:Tera,它是一个大型分布式表格存储系统,可以进行高性能、可伸缩的半结构化存储,同时支持存储万亿量级的超链和网页信息。Tera系统有八个核心技术:1、数据模型的全局有序;2、实时的读写和区间扫描(这一点与数据模型的全局有序也密不可分);3、可以支持行式存储和列式存储;4、友好的分布式文件系统;5、利用SSD cache热数据;6、数据压缩,异步IO和分组提交等性能优化手段;7、支持秒级分裂合并,并且能够实现自动负载均衡;8、在分布式数据库上实现了分布式事务。Tera是百度搜索引擎从批量处理迈向实时流式计算的最基础的架构,它可以提供实时的读写能力,同时提供海量存储和增量计算,并且节约增量成本。在研发方面,Tare也能够使中间数据可见、Debug能力增强。本质上来说,Tera是一个分布式存储系统,它需要遵循分布式存储系统的设计要素,包括:明确数据模型、存储引擎的设计、数据分片的方式、如何管理元数据、高可用的设计以及应用的是分层式还是竖井式的存储。同时,存储系统也需要进行性能优化工程的实践,包括指标数据的采集和可视化、先做profiling再手动优化、面向SSD进行编程、Batch&pipeline&asynchronous和学习存储引擎的先进研究成果。京东数科高级DBA潘娟《Sharding-Sphere云架构演化》互联网应用业务的特点是用户和数据量大、产品迭代迅速、业务组合复杂、突发性流量暴增以及7*24小时不间断提供服务,这些特点导致互联网架构从一开始的单体式架构发展到分布式微服务,再到云原生架构。分布式微服务使得系统解耦可用性得到提升,而云原生架构更好地实现了资源按需伸缩、自动化的部署和管理。同时,互联网数据库也在不断地升级,由于早期的RDBMS无法满足数据扩展的需求,NoSQL应运而生。再到后来,囊括分布式数据库、分布式数据库中间件以及云数据库的NewSQL也都纷纷顺应数据扩展的需求而产生。作为分布式数据库的中间件,Sharding-Sphere可以借助底层成熟的关系型数据库进行增量持续的开发,这满足了分布式的需求,并且能够大大降低运维和接入成本。同时,Sharding-Sphere也应该拥有四种核心能力:1、能够通过数据拆分或读写分离,实现数据分片的能力。具体来讲,数据分片需要先通过SQL解析,对查询语句进行优化、合并和改写,再找到可以执行的SQL,最终实现结果归并。2、分布式事务能够保证数据的一致性。在数据还未打散之前,我们可以用传统的ACID(Atomicity、Consistency、Isolation、Durability)保障数据的一致性;但在数据被打散之后,我们则需要引进新的BASE原则(Basically Available、Soft state、Eventual consistency)来保障数据一致。有两种方式可以实现分布式事务BASE:一种为XA,虽然XA支持回滚,能够达到强一致性,但当并发量变大,它的性能会急剧下滑;而另一种方式是柔性事务,在这种方式下,当业务量急剧上升时,并发性能只会发生略微衰退,并且能够保证,在最终达到一致。3、数据库治理。Sharding-Sphere可以通过注册中心的操作,实现统一管理、熔断或失效转移的功能,同时可以通过拓扑图和调用链来打造APM监控。4、弹性伸缩。Sharding-Sphere是由 Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar这3款相互独立的产品组成的,他们均提供标准化的数据分片、分布式事务和数据库治理功能。Sharding-JDBC更适用于单体应用和分布式微服务,它是一个轻量级的Java框架;Sharding-Proxy实现了MySQL的二进制协议,并且不存储任何数据;Sharding-Sidecar的核心是Service Mesh Sidecar,能够帮助本地的应用层与网络节点进行沟通。

January 16, 2019 · 1 min · jiezi

用docker安装mysql5-6,并远程连接

用docker安装mysql:5.6镜像运行命令: docker pull mysql:5.6查看下载的docker镜像运行命令: docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEmysql 5.6 27e29668a08a 2 weeks ago 256MB创建docker容器tips: MySQL(5.7.19)的默认配置文件是 /etc/mysql/my.cnf 文件。如果想要自定义配置,建议向 /etc/mysql/conf.d 目录中创建 .cnf文件。新建的文件可以任意起名,只要保证后缀名是 cnf 即可。新建的文件中的配置项可以覆盖 /etc/mysql/my.cnf 中的配置项。创建要映射到容器中的.cnf文件mkdir -p docker_v/mysql/confcd docker_v/mysql/conftouch my.cnf启动容器docker run -p 3306:3306 –name mysql -v /opt/docker_v/mysql/conf:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=123456 -d imageID-p 3306:3306:将容器的3306端口映射到主机的3306端口-v /opt/docker_v/mysql/conf:/etc/mysql/conf.d:将主机/opt/docker_v/mysql/conf目录挂载到容器的/etc/mysql/conf.d-e MYSQL_ROOT_PASSWORD=123456:初始化root用户的密码-d: 后台运行容器,并返回容器IDimageID: mysql镜像ID查看启动的mysql容器运行命令: docker psnavicat远程连接mysql新建连接点击新建连接,选择mysql更改配置在常规选项中,输入主机名,mysql的端口号,mysql的用户名和密码在SSH选项中,输入主机ip,主机端口号,主机用户名和密码点击测试连接

January 16, 2019 · 1 min · jiezi

容器,Docker, Kubernetes和Kyma,以及Kyma对SAP的意义

大家好,今天非常高兴能给大家做一个关于Kyma的技术分享。这个session的audience主要是针对使用咱们成都研究院使用Java和nodejs等技术栈做微服务开发的同事们。对于在ABAP netweaver上做SAP传统开发的同事们来说,这个session可以让大家开阔一下眼界。这是今天session的agenda:•Why Containers?•Relationship between Containers and Dockers?•Why Kubernetes?•Relationship between Kyma and Kubernetes•A real example: Commerce cloud extension via Kyma之所以要在Kyma真正开始前做容器,Docker,Kubernetes的铺垫,是因为Kyma基于Kubernetes,而Kubernetes又是容器编排框架,Docker又是一种流行的容器运行时实现技术,如果不提Kubernetes,Docker和容器,没有接触过这些概念的同事一定会觉得一头雾水。我们先来看容器。我们说任何技术都有其使用场景和解决的痛点。那么为什么这些年来容器技术非常火呢?得益于近些年微服务架构的火热,很多企业包括SAP自己也在开发基于微服务架构的新应用,比如在坐很多同事所在团队正在做的事情。而基于微服务架构的SaaS软件开发,业界有一套标准,或者说是最佳实践,那就是著名的twelve-factor标准:https://12factor.net/zh_cn/而容器,就是一种有助于开发人员以更小的代价去开发一个满足这12个准则的基于微服务架构的云原生应用的技术。比如这个准则里提到的,微服务应用的build,release和运行阶段应该严格区分,应用通过一个或多个无状态的进程进行执行,彼此隔离,通过进程模型进行水平扩展,等等,这些通过容器技术都可轻易实现,不需要开发人员付出额外代价。因此,我们需要记住一个结论,容器的使用场景,永远是和微服务架构,SaaS,云原生应用这些紧密相连的。那么容器具体来说到底是一个什么东西呢?字面意思,用来装东西的集装箱。装什么东西?除了应用程序本身之外,还包括这个应用要能正常运行所需的运行环境和库文件等外部依赖。我们想象一下现实世界中的集装箱。一辆汽车从码头上被装到集装箱里,然后被货船载到另一个码头里。这里的汽车就好比我们的应用,集装箱就是容器,汽车在不同的码头上装入集装箱就好比应用的部署。这就是slide里第一条,Convenient package to ship things的概念。Open specification:要注意,容器 != Docker。Docker只是容器技术的一种商业化实现方案。在2015年,由Google,Docker、CoreOS、IBM、微软、Redhat等厂商联合起来,成立了一个OCI(Open Container Initiative)组织,并于推出了第一个开放容器标准,旨在避免容器技术的碎片化。该标准主要分为运行时标准和容器镜像标准。Isolated:容器隔离。这个很好理解,容器里运行的应用彼此之间是隔离的,一个应用出故障不会影响到其他容器。可以独立分别进行水平扩展。Portable:既然容器封装了所有运行应用程序所必需的相关的细节,比如应用依赖以及操作系统,这就使得镜像从一个环境移植到另外一个环境更加灵活。比如,同一个镜像可以在Windows或Linux,开发、测试或生产环境中运行。基于容器的应用,既能运行在开发者的笔记本电脑上,也能运行在云服务提供商的数据中心上。真正做到一次构建,到处运行。LightWeight:轻量级。虚拟机和容器的目的类似,都致力于对应用程序及其关联性进行隔离,从而构建起一套能够不依赖于具体环境而运行的应用单元。虚拟机是在物理服务器的上层用软件来模拟特定的硬件系统。Hypervisor位于硬件和系统之间,是创建虚拟机必须的一个部分。虚拟机软件必须使用Hypervisor作为一个中间层,是虚拟机技术的核心,当宿主操作系统启动虚拟机时,会通过hypervisor给虚拟机分配内存,CPU,网络和磁盘等资源,并加载虚拟的操作系统,因而需要消耗宿主机大量的物理资源。一台宿主机上运行的多个容器化应用共享这台宿主机操作系统的内核,因而不需要虚拟机技术的hypervisor中间层,因而同虚拟机技术相比,更加轻量化,启动速度更快。那么容器和docker的关系又是怎样的?前面已经说到了,Docker只是基于开放容器标准的一种比较受欢迎的实现。Docker之于容器,相当于React,Angular和Vue之于UI开发框架。既然大多数时候我们在谈到容器时,都会不自觉地想到Docker,那么Docker到底是用什么实现的呢?著名的计算机科学家王垠,曾经在他的个人博客上撰文,声称Docker和Kubernetes并不是什么了不起的技术。从科学家的角度出发,这个论断不能算错误,因为Docker底层确实就是对Linux里很多原语做了很好的封装,所以从商业化的角度取得了成功。以下是一些Docker封装的Linux系统原语的一些例子。Jerry是SAP Docker和Kubernetes培训课程的讲师之一,在这个课程上,我们会对Docker如何凭借这些原语实现开放容器标准做深入的讨论。接下来,我们引入Kubernetes。为什么有了Docker后,还需要Kubernetes?我们知道从结果上看,Docker和虚拟机都可以做到让应用在隔离的环境下运行,区别在于Docker运行环境仍然能够和宿主机共享操作系统内核,而虚拟机则通过付出更多宿主机系统资源的代价,构造出一个完全虚拟的操作系统,让应用在里面运行。然而Docker容器和虚拟机还是有一些问题没有解决,就是容器在大型分布式集群上的部署,微服务应用中的容器管理和协同,自动地水平扩展,自动修复和弹性伸缩等等。这也是Kubernetes大显身手的地方。诞生于2015年7月的Kubernetes,是Google内部多年使用的容器集群管理系统Borg的开源版本。由于凝聚了Google在容器编排领域多年的深厚功力,发布之后很快就一飞冲天,如今已经成为事实上的容器集群管理领域的标准和霸主。Kubernetes源自古希腊语,意为“舵手”。有人调侃说,Google选择Kubernetes这个单词,暗示了自己想在容器编排管理这个领域里扮演舵手和领导者的角色。Kubernetes和Docker容器的关系?下面这张图片是SAP Kubernetes培训课程slide里的一张,用来说明Kubernetes和docker容器的关系,我觉得很形象。运行了各种微服务应用的容器就好比图中使用各种乐器演奏的音乐家,而站在中间的指挥家,和使用乐器演奏的音乐家站立的台阶,就相当于Kubernetes。如果更准确的说,Kubernetes管理的不是容器,而是pod。Pod是一个或者多个容器组成的集合。至此,我们终于完成了了解Kyma必须的前置知识的介绍。什么是Kyma?去年6月份,SAP C/4HANA正式announce时,这张图在大家的朋友圈中都刷屏似的存在。大家可以看到,Slide里的描述,SAP云平台扩展工厂是一个基于云端原生微服务的通用创新和敏捷平台。那么云平台扩展工厂和括号里的Kyma关系又如何?二者的关系恰如Open UI5和Fiori的关系。Open UI5是SAP推出的一个开源UI开发框架和UI控件库,而Fiori是SAP 基于Open UI5这个技术框架开发出来的商业化产品(当然现在Fiori也代表SAP推荐的一种UI风格)。类似的,SAP Cloud Platform Extension Factory是SAP基于Kyma这个开源项目,再针对企业应用所必须满足的一些标准(比如SAP产品标准,区域特殊需求)而添加进额外的附加功能和实现的商用产品。Kyma对C/4HANA意味着什么?我们CX部门的CTO Moritz Zimmermann, 在他的linkedin上发表过一篇博客,里面也提到了Kyma:Kyma(SAP Cloud Platform Extension Factory)将来会成为SAP C/4HANA套件里所有基于微服务架构产品的统一扩展工具。Kyma是基于Kubernetes的,这也是我们之前花了很多时间进行Docker和Kubernetes介绍的原因。那么Kyma的工作原理是什么?简单的说就是一个观察者-发布者模式。1. 通过Application Connector,可以使Kyma同SAP C/4HANA的产品建立连接,然后进行事件注册。2. 事件注册好之后,使用微服务架构实现事件的监听者(消费者)。这也是Kyma官网里提到的"开发者可以使用任何技术栈进行扩展开发“的含义。举个例子,我们在SAP Commerce Cloud里创建一个订单后,客户提出了基于该企业流程的一些特殊校验逻辑。Commerce Cloud发布一个"Order Create"的事件,事件payload包含创建订单的字段。我们开发并部署在Kyma上的微服务监听这个事件,微服务内部实现可以采取任何技术栈,Commerce Cloud通过HTTP调用包含了企业自定义订单校验逻辑的微服务,根据其返回的校验结果进行后续处理。我们来看一个具体的demo,看看SAP Commerce Cloud里订单编排功能是如何用Kyma去增强的。下图蓝色流程是我们通过Kyma对Commerce Cloud的标准流程进行的增强,主要是在下单过程中增加了一些Validation校验。我们登录commerce的back office页面,定义一个新的action:然后进到Kyma的console页面:选择一个stage进去,点击Lambdas进入编辑页面:新建一个Lambda function,取名fraudcheck2:这个function自动创建的标签(Labels),Kubernetes老司机一定觉得很亲切。这些标签其实和大家现实工作中使用云笔记里的标签和图片管理软件里的标签作用相同,就是一种键值对(Key Value Pair), 可以允许用户把Kubernetes的对象能灵活的分组,并提供高效的检索。Function Trigger里可以指定这些Lambda函数在哪些事件触发后执行。选择第一步定义新的action后对应的event:Lambda函数具体的实现,做过nodejs开发的朋友们一定不会觉得陌生。首先第18行,19行从event这个输入参数对象里取得发生事件的订单Code,然后第26行消费OCC(Omni Commerce Channel)Restul API获得订单明细,从明细里获得订单的客户ID,再调用第30行的代码根据客户ID拿到客户明细,然后使用第37行和第40行的代码分别检查该客户的邮箱地址是否有效,以及该客户是否第一次下单。注意第43行和46行的代码我暂时注释掉,稍后才会启用。现在我们来测试一下。在Commerce里下一个单,记下订单ID。回到Commerce back office页面,查看刚才下的订单的Business Process:这里看到了刚才第一步新建的基于Kyma Action对应的流程日志记录:我们再去查看这个订单的Fraud检查记录:点这个Fraud Reports查看检查结果。这个标签从左到右依次排开的风格很像Fiori和ABAP Webdynpro。可以看见前文介绍的Email和是否是首单的检查结果。Email检查结果,客户的邮箱地址有效。现在再回到Kyma的Lambda函数编辑器里,将之前注释掉的从Marketing Cloud获取联系人地址的函数以及调用SAP云平台的Business Partner服务的函数重新启用:启用之后,保存,然后进入Service Catalog。这个组件也是Kubernetes提供的标准组件,Kyma基于它做了增强,能够将第三方的服务导入进来给Kyma的Lambda函数消费。接下来的步骤和我们在SAP云平台上消费一个服务类似,首先创建一个服务实例:然后再基于这个服务实例创建一个绑定,绑定类型设置成Function Binding,绑定的目标设置成之前编辑好的Lambda函数。再下一个单:这一次,这个第二次下的订单的Fraud检查报告,同第一个订单相比就多了两条记录:首先看第二条首单检查的记录,得分为0,和我们期望的结果一致。从Marketing Cloud的服务返回的检查结果:从SAP云平台的Business Partner服务返回的结果可以看出,下单的这个客户不存在一个对应的Business Partner。至此关于如何使用Kyma对SAP Commerce产品的订单编排做增强就简单介绍到这里,感谢阅读。要获取更多Jerry的原创文章,请关注公众号"汪子熙": ...

January 15, 2019 · 1 min · jiezi

基于SAP Kyma的订单编排增强介绍

尽管有一万个舍不得,2018年还是无可挽回地离我们远去了。唯有SAP成都研究院的同事和我去年在网络上留下的这些痕迹,能证明2018年我们曾经很认真地去度过每一天:SAP成都研究院2018年总共87篇技术文章合集一个SAP开发人员的2018年终总结今天写的这篇文章也是因为工作需要。本文会首先介绍SAP传统产品里的订单编排增强技术,再来了解一下同样的增强需求,SAP Kyma是如何完成的。目录基于SAP传统ABAP技术的订单编排增强技术基于SAP Kyma的订单编排增强技术SAP产品里的订单处理流程,无论是On-Premises解决方案还是云产品,我认为归根到底可以概括成四个字:订单编排,包含两个层次的内容:1. 单个订单通过业务流程或者工作流驱动的状态迁移,比如从初始的Created状态,经过一系列操作,最后进入Closed状态;2. 多种类型的订单协同工作,完成一个完整的端到端业务流程。典型的例子有销售自动化(Sales Force Automation)里的从Lead, Opportunity, Quotation到Contract,Order这些不同类型的订单协同。SAP系统里订单状态的迁移到底有多复杂?复杂度绝对远超初学者的想象。以SAP CRM为例,在我使用的SAP CRM 714系统里,订单状态总共有906种,这不得不让人佩服SAP CRM当初的设计者考虑问题的周全。即便已经设计了这九百零几种状态,SAP仍然考虑到了客户可能的状态扩展需求,因此采用了一种经典的User Status(用户自定义状态)和System Status(SAP标准状态)的两层状态设计,让用户能够随便定义的User Status通过扮演中间层角色的Business Transaction,映射到能够被SAP标准程序所感知的System Status。上图左边的Open, In process, Released和Completed就是用户自定义订单状态,SAP允许客户给每个状态分配一个Low和High的值,通过这两个值巧妙地提供了一种用非图形化方式进行状态跳转的功能。比如In process状态的Low为20,意味着In process状态不可能重新回到Open状态,因为Open状态的ID 10小于In process状态的Low字段定义的20——一个状态能跳转到的目标状态的ID,必须位于由该字段的Low和High定义的区间内。除了复杂的状态处理和跳转外,SAP订单编排的复杂度主要体现在以下方面:1. 很多SAP的客户,除了购买SAP的On-Premises产品或者订阅云服务外,还拥有其他业务系统。这类客户的订单编排,在SAP标准业务流程基础上往往还存在和这些第三方业务系统的交互。2. 即使是同一行业的客户群,因为地域和国家,语言的差异,可能业务流程也存在一定的区别。SAP发布的标准功能有时无法100%支持这些在细节上存在千差万别的业务流程。因此SAP系统对订单编排增强的支持就非常必要。当然,不同的SAP产品,对订单增强的实现方式也各不相同。在SAP CRM里,虽然SAP没有明确提出Business Object这个概念,但订单应用基于的模型实际上仍然是由不同的节点组成:每个节点对应一些更底层的模型节点,其上可以注册各种事件处理函数。下图是Service Request这个BO的抬头节点的事件处理函数:每种事件触发时,注册的函数会自动执行。每个节点可以分配一个或多个执行函数。当然,严谨的德国人在最简单的观察-发布者模式上又添加了几个维度的设置。下图第一列红色的Execution Time,表示这些分配的函数到底是事件触发后立即执行,还是延迟到订单抬头或者行项目的通用例程执行完后再执行(往往用于实现批量操作,或者待执行函数同通用例程存在依赖关系,或者出于性能考虑)。第二列的Priority,即函数执行优先级,如果若干函数除了优先级外其他维度维护的属性完全一致,则按优先级从高到低依次执行。第三列Event,就是观察者-发布者模式里的事件了,下面是SAP CRM订单框架一些标准的事件:最后一列就是事件监听函数。Jerry倾向于把CRM订单处理系统的运作方式理解成类似下图这种复杂的水管传输系统,订单业务流程依次通过注册在不同事件上的监听函数去执行,就像水从这一根根大小粗细长短各异的水管流过一样。如果客户对其中某个业务步骤需要做增强(需要替换某根水管), 只需要用一个自己实现的函数去替换SAP标准函数(自己另外找一根水管替换掉现在正在工作的水管),能替换的前提是自己实现的函数的接口同被替换函数完全一致(自己另外找的水管和以前的水管两端接口的规格完全一致)。而SAP Cloud for Customer里的订单模型,其Business Object在目前最新的1811版本里仍然是由ESF2框架实现,只是后台对Partners不可见,但大家可以类比SAP On-Premises世界里的BOPF框架,两个框架的实现原理类似。在Cloud世界里,想对订单处理流程做增强,同之前介绍的SAP CRM相比,相对来说受的限制要多一些。在Partner做增强开发的Cloud Application Studio里,所有能做增强的点以Hook的方式显示如下:Partners可以在这些Hook里进行业务功能增强开发。有些Hook可能存在某些读写限制,比如AfterLoading这个Hook,会在SAP BO的标准加载逻辑执行完毕后被调用,在这个Hook的实现里,SAP不允许任何对BO节点标准字段的写操作,以避免Partners的增强对SAP标准流程可能带来的影响。有的顾问朋友可能会说,这些Hook不就是SAP Netweaver里传统的Business AddIn(BAdI)么?没错,概念上可以这么理解,需要提醒的就是,这些Hook创建之后,在ABAP后台并不是以BAdI Implementation的方式存储,而是以ESF2 Determination的方式存储,类似下图这种BOPF里的Determination:我们再来了解一下用SAP Kyma如何完成类似的需求。在使用Kyma之前,大家得对Kyma是什么,SAP为什么要开发出Kyma这两个问题比较清楚才行。我之前的文章 站在巨人肩膀上的牛顿:Kubernetes和SAP Kyma 已经介绍了这两个问题的答案,所以本文不再重复,直接上实例了。我们假设需要对SAP Hybris Commerce的下单流程做增强,在标准的Fraud(欺诈)检查里加入我们在Kyma里实现的自定义检查流程。如下图所示,其中浅蓝色的矩形框代表我们用Kyma实现的自定义Fraud检查逻辑。具体增强了哪些检查逻辑呢?从下的订单里拿到下订单的客户ID,然后在Kyma里调用SAP Marketing Cloud和SAP云平台上提供的服务对这个客户做校验,Kyma拿到校验结果后,再传回Commerce。下面是具体步骤。1. 首先登录Commerce的back office页面,定义一个新的action,ID为EXTERNAL_KYMA_FRAUD_CHECK。做过ABAP开发的朋友们不难理解这个action,可以类比成ABAP里的action profile,用于存储和维护Partner的增强。2. 进到Kyma的console页面:选择一个stage进去,点击Lambdas进入编辑页面:新建一个Lambda function,取名fraudcheck2。我们所有的增强逻辑就写在这个函数里。这个函数自动创建的标签(Labels),Kubernetes老司机们一定觉得很亲切。这些标签其实和大家现实工作中使用云笔记里的标签,以及图片管理软件里的标签作用相同,就是一种键值对(Key Value Pair), 可以允许用户将Kubernetes对象进行灵活分组,并提供高效的检索。这个标签的概念不是Kyma发明的,而是Kubernetes的标准功能。Function Trigger里可以指定这些Lambda函数在哪些事件触发后执行,思路和前文介绍的SAP CRM函数注册一致。选择第一步定义了新的action后对应的event:关于Lambda函数具体的实现,做过nodejs开发的朋友们一定不会觉得陌生。首先第18行,19行从event这个输入参数对象里取得发生事件的订单Code,然后第26行消费OCC(Omni Commerce Channel)Restul API获得订单明细,从明细里获得订单的客户ID,再调用第30行的代码根据客户ID拿到客户明细,然后使用第37行和第40行的代码分别检查该客户的邮箱地址是否有效,以及该客户是否第一次下单。注意第43行和46行的代码我暂时注释掉,稍后才会启用。现在我们来测试一下。在Commerce里下一个单,记下订单ID 2139。回到Commerce back office页面,查看刚才下的订单的Business Process,输入2139进行查询:这里根据ID EXTERNAL_KYMA_FRAUD_CHECK进行搜索,找到了刚才第一步新建的基于Kyma action对应的流程日志记录:我们再去查看这个订单的Fraud检查记录:点这个Fraud Reports查看检查结果。这个标签从左到右依次排开的风格很像Fiori和ABAP Webdynpro。可以看见前文介绍的Email有效性检查和是否是首单的检查结果。Email检查结果:客户的邮箱地址有效。首单检查返回的分数是100,根据当前Commerce配置文件这个结果被认定为首单。具体配置文件的位置和本文主题无关,这里不详述。现在再回到Kyma的Lambda函数编辑器里,将之前注释掉的从Marketing Cloud获取联系人地址的函数以及调用SAP云平台的Business Partner服务的函数重新启用:启用之后,保存,然后进入Service Catalog。这个组件也是Kubernetes提供的标准组件,Kyma基于它做了增强,能够将第三方的服务导入进来给Kyma的Lambda函数消费。这里能看到已经导入了很多第三方服务。我们其实可以把这个界面类比成SAP云平台的Service Market Place。选择SAP云平台的Business Partner Service:接下来的步骤和我们在SAP云平台上消费一个服务类似,首先创建一个服务实例:然后再基于这个服务实例创建一个绑定,绑定类型设置成Function Binding,绑定的目标设置成之前编辑好的Lambda函数。现在再下一个单试试,下单客户选择同第一个订单相同的客户:这一次,这个第二次下的订单的Fraud检查报告,同第一个订单相比就多了两条记录:首先看第二条首单检查的记录,得分为0,和我们期望的结果一致,因为这已经不是该客户第一次下单了:从Marketing Cloud的服务返回的检查结果:从SAP云平台的Business Partner服务返回的结果可以看出,下单的这个客户不存在一个对应的Business Partner。本文这个例子,在Commerce下单流程中通过Kyma去访问Marketing Cloud和SAP云平台上的服务进行额外的Fraud检查,业务上来说可能意义不大,更多的是从技术的角度出发,介绍了这种基于微服务架构的订单编排增强方式。祝大家新年快乐! 相关阅读站在巨人肩膀上的牛顿:Kubernetes和SAP Kyma在Kubernetes上运行SAP UI5应用(上)在Kubernetes上运行SAP UI5应用(下)要获取更多Jerry的原创文章,请关注公众号"汪子熙": ...

January 15, 2019 · 1 min · jiezi

为什么要用docker?

本文旨在用最通俗的语言讲述最枯燥的基本知识最近Docker突然火得不得了,到处都是谈论Docker的声音,相信大家和小编一样的心情,看这个东西有点高大上,但尝试去阅读Docker文章时又发现概念很模糊、不接地气、难以理解、无从下手…于是三天打鱼两天晒网,最终不了了之,反正公司也没要求用这玩意儿,不费劲了…这不,当前几天项目要求快速上线并且部署多台服务器环境时,小编一台一台服务器的yum install、vim、restart…想屎的心都有,那时小编心里想要是有一个这么一个U盘,能把整个环境一台一台的Ctrl+V过去,那该多好啊。那时脑子一下子闪过一个念头:Docker不就是我想拥有的那个U盘吗。怀着这样的一个念头,小编花了一些时间去查阅Docker相关的书籍、看视频、逛论坛,刚开始学习时非常难受,搞不懂什么是容器什么是镜像什么是鲸鱼船…而涉及到一些原理底层的知识,书籍里的那些概念比代码都能懂,小编硬啃了一段时间并且实操了几次之后,再回过头来,才豁然开朗。念及许多想要学Docker却苦于难以入门的开发者们,正在学习却很挣扎的初学者们,故而把小编这段时间的学习成果,以一个初学者的角度,遵循循序渐进的原则,编成一份通俗易懂的文章,希望能以此引导入门,早日成Docker大神。文章提纲:什么是DockerDocker对我们有什么用处Docker安装和使用彩蛋1. 什么是Docker什么是docker?恐怕90%的人脑子一闪而过的都是那张图:一条鲸鱼背上扛着一堆箱子,图片下方是大大的“docker”。docker是什么?是鲸鱼?还是一堆箱子?或者说是载着箱子的鲸鱼?….这里小编不做解释,先引用一下官方的解释:Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。顾名思义,docker是一个容器引擎,容器且不说,什么是引擎?我们知道:汽车的引擎是发动机,有了发动起,汽车才能跑起来游戏需要游戏引擎(如Unity3D..),基于引擎的开发,能让游戏动起来。如官言,docker也是一种引擎,基于这个引擎,开发者能让他们开发的应用处于一个有隔离性的、可移植性的容器中,以便于发布于各种机器中而无需考虑兼容性问题。因此就不难解释,为什么docker的logo是一条装着一堆箱子的鲸鱼这是因为:docker扮演的是图中鲸鱼的角色,而鲸鱼之上的集装箱就是一个个容器,容器中是我们开发的应用程序(不仅限于web应用),每个容器都有自己独立的环境(环境设置、网络、文件系统…),互不干扰。而每个箱子,又可以打包成一个新的镜像,放到其它服务器的docker环境中直接运行,不再需要重复安装程序运行环境。上面的解释插入了两个生疏的概念:镜像容器我们先讲容器容器,顾名思义:就是装东西的器皿,在docker中,容器就是装载我们的应用程序的器皿,在docker的logo中,容器就是一个个箱子。我们知道,运行JavaWeb应用程序需要有Tomcat,那么我们就需要创建一个Tomcat的容器,才能把我们的程序放进去运行。那么,容器哪里来的呢我们知道,给电脑安装Windows系统需要有Windows镜像,因此给docker安装容器也是需要镜像的,所以,通俗一点,镜像就类似于我们日常中的安装软件,甚至说是操作系统镜像更为形象点。那容器和镜像有什么关系?网上说是类和对象的关系,没错,但是这样的比喻没什么实际卵用。我们知道:要运行一个web程序,需要有个Tomcat环境,需要Tomcat环境,那就下载一个Tomcat解压出来,然后把web程序放入Tomcat的webapps中启动即可,那么在docker中要运行一个web程序,就需要有Tomcat容器,需要Tomcat容器,就得去下载Tomcat镜像(也可以自己构建),把镜像pull下来之后,运行起来,就是一个Tomcat容器,此时把web程序至于Tomcat挂载的数据目录webapps中既可以运行。所以docker中,容器和镜像的关系更像是一种动静的关系,也就是说,存于仓库中的镜像是一个死的软件,而运行起来的容器则像是一个正在运行的程序(进程)。2.Docker有什么用对docker有了清晰的了解之后,我们心里可能在想:这玩儿对我有什么用?我在哪里能用得上?怎么用才对?这里引用几个案例来让大家体会体会。1.案例一前几天,公司一批服务器就要到期了,由于服务器是15年购买的,硬件的性能远比现在新出的云主机低,因此决定把所有服务器都换成新一代服务器,但是小编整准备动手迁移服务器时,内心一阵阵崩溃感涌上心头,仔细一算,每台服务器都要做同样的事情:安装jdk、Tomcat、nginx配置jdk环境变量和系统变量配置Tomcat配置nginx安装项目所需的视频解码组件导入项目所需的一些特殊字体后来决定用docker部署的办法,在每台服务器都把docker安装之后,只需要在其中一台服务器中把Tomcat镜像从镜像仓库拉取下来,把这些配置都设置好,做成一个自己的镜像上传到镜像仓库中,之后在其他几台服务器都下载自己做的镜像,运行于docker中,把代码上传,就万事大吉了。案例二前不久的圣诞活动中,公司临时的活动方案在程序员的加班加点中终于上线,但是一上线之后发现推广海报中的中文名字乱码,领导问责测试人员怎么没做好测试,测试很委屈的说我已经测试无数遍并且测试报告都提交了,解决了所有问题才上线的;没办法只能让服务器同事查看正式服务器中的tomcat配置,发现原来 tomcat用了默认编码方式:iso8859-1,而测试环境中是UTF-8。针对这个问题,项目组决定把开发环境迁移到docker中,在测试环境中测试无误后,把镜像打包发布到正式环境中,解决了环境不同导致的问题。3. docker的安装感知到docker对我们开发者的好处之后,相信大家也跃跃欲试了吧,下面就Linux centOS(版本大于等于7.0)系统进行演示docker的安装步骤:1.删除可能存在的旧版本sudo yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-selinux \ docker-engine-selinux \ docker-engine安装一些必备工具:yum install -y yum-utils device-mapper-persistent-data lvm2添加Docker源:yum-config-manager \ –add-repo \ https://download.docker.com/linux/centos/docker-ce.repo更新yum缓存yum makecache fast安装yum install docker-ce6.创建docker用户温馨提示:以下操作不是必备,但是为了养成一个Linux用户的使用的良好习惯,小编不建议直接使用root操作,因此创建一个用户用来操作docker。useradd docker_managerdocker_manager是用户名,可以根据喜好起名,创建完用户之后,设置密码passwd docker_manager docker_manager_wkt会提示输入密码和确认密码,按照提示操作即可配置docker用户的权限visudovisudo是配置用户权限的文件,在命令行中输入visudo回车之后,会进入文件操作,找到“# %wheel ALL=(ALL) NOPASSWD: ALL”这一行,把前面的“#”去掉,保存文件授权usermod -aG wheel,docker docker_manager启动 Docker CEsudo systemctl enable dockersudo systemctl start docker此时在命令行中输入“docker info”之后,能够展示docker相关信息,就表示docker安装成功。docker info镜像加速:编辑daemon.json文件(首次安装daemon.json是个新文件,不要惊讶)vim /etc/docker/daemon.json加入以下内容:{ “registry-mirrors”: [ “https://registry.docker-cn.com” ]}保存,sudo systemctl restart docker重启即可。4.彩蛋上面针对docker使用了一种比较通俗诙谐的语言来讲述,也是为了方便读者能容易的解读和理解,理解并且成功安装docker之后,你们就会想方设法的要把自己的web项目部署到docker了,这我没说错吧,大家可以网上查找一些资料去学习这一块,当然也可以选择:关!注!我!下篇文章我会对docker网络、数据卷、常用操作命令和创建自己的docker镜像、上传镜像等做一个全面的解答,之后会docker部署web项目的流程、以及运行Redis、MongoDB、nginx等常用软件做一些例子讲解。觉得本文对你有帮助?请分享给更多人关注「编程无界」,提升装逼技能 ...

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

容器监控实践—Metrics Server

概述从 v1.8 开始,资源使用情况的监控可以通过 Metrics API的形式获取,具体的组件为Metrics Server,用来替换之前的heapster,heapster从1.11开始逐渐被废弃。Metrics-Server是集群核心监控数据的聚合器,从 Kubernetes1.8 开始,它作为一个 Deployment对象默认部署在由kube-up.sh脚本创建的集群中,如果是其他部署方式需要单独安装,或者咨询对应的云厂商。Metrics API介绍Metrics-Server之前,必须要提一下Metrics API的概念Metrics API相比于之前的监控采集方式(hepaster)是一种新的思路,官方希望核心指标的监控应该是稳定的,版本可控的,且可以直接被用户访问(例如通过使用 kubectl top 命令),或由集群中的控制器使用(如HPA),和其他的Kubernetes APIs一样。官方废弃heapster项目,就是为了将核心资源监控作为一等公民对待,即像pod、service那样直接通过api-server或者client直接访问,不再是安装一个hepater来汇聚且由heapster单独管理。假设每个pod和node我们收集10个指标,从k8s的1.6开始,支持5000节点,每个节点30个pod,假设采集粒度为1分钟一次,则:10 x 5000 x 30 / 60 = 25000 平均每分钟2万多个采集指标因为k8s的api-server将所有的数据持久化到了etcd中,显然k8s本身不能处理这种频率的采集,而且这种监控数据变化快且都是临时数据,因此需要有一个组件单独处理他们,k8s版本只存放部分在内存中,于是metric-server的概念诞生了。其实hepaster已经有暴露了api,但是用户和Kubernetes的其他组件必须通过master proxy的方式才能访问到,且heapster的接口不像api-server一样,有完整的鉴权以及client集成。这个api现在还在alpha阶段(18年8月),希望能到GA阶段。类api-server风格的写法:generic apiserver有了Metrics Server组件,也采集到了该有的数据,也暴露了api,但因为api要统一,如何将请求到api-server的/apis/metrics请求转发给Metrics Server呢,解决方案就是:kube-aggregator,在k8s的1.7中已经完成,之前Metrics Server一直没有面世,就是耽误在了kube-aggregator这一步。kube-aggregator(聚合api)主要提供:Provide an API for registering API servers.Summarize discovery information from all the servers.Proxy client requests to individual servers.详细设计文档:参考链接metric api的使用:Metrics API 只可以查询当前的度量数据,并不保存历史数据Metrics API URI 为 /apis/metrics.k8s.io/,在 k8s.io/metrics 维护必须部署 metrics-server 才能使用该 API,metrics-server 通过调用 Kubelet Summary API 获取数据如:http://127.0.0.1:8001/apis/metrics.k8s.io/v1beta1/nodeshttp://127.0.0.1:8001/apis/metrics.k8s.io/v1beta1/nodes/<node-name>http://127.0.0.1:8001/apis/metrics.k8s.io/v1beta1/namespace/<namespace-name>/pods/<pod-name>Metrics-ServerMetrics server定时从Kubelet的Summary API(类似/ap1/v1/nodes/nodename/stats/summary)采集指标信息,这些聚合过的数据将存储在内存中,且以metric-api的形式暴露出去。Metrics server复用了api-server的库来实现自己的功能,比如鉴权、版本等,为了实现将数据存放在内存中吗,去掉了默认的etcd存储,引入了内存存储(即实现Storage interface)。因为存放在内存中,因此监控数据是没有持久化的,可以通过第三方存储来拓展,这个和heapster是一致的。Metrics server出现后,新的Kubernetes 监控架构将变成上图的样子核心流程(黑色部分):这是 Kubernetes正常工作所需要的核心度量,从 Kubelet、cAdvisor 等获取度量数据,再由metrics-server提供给 Dashboard、HPA 控制器等使用。监控流程(蓝色部分):基于核心度量构建的监控流程,比如 Prometheus 可以从 metrics-server 获取核心度量,从其他数据源(如 Node Exporter 等)获取非核心度量,再基于它们构建监控告警系统。官方地址:https://github.com/kubernetes…使用如上文提到的,metric-server是扩展的apiserver,依赖于kube-aggregator,因此需要在apiserver中开启相关参数。–requestheader-client-ca-file=/etc/kubernetes/certs/proxy-ca.crt–proxy-client-cert-file=/etc/kubernetes/certs/proxy.crt–proxy-client-key-file=/etc/kubernetes/certs/proxy.key–requestheader-allowed-names=aggregator–requestheader-extra-headers-prefix=X-Remote-Extra—requestheader-group-headers=X-Remote-Group–requestheader-username-headers=X-Remote-User安装文件下载地址:1.8+,注意更换镜像地址为国内镜像kubectl create -f metric-server/安装成功后,访问地址api地址为:Metrics Server的资源占用量会随着集群中的Pod数量的不断增长而不断上升,因此需要addon-resizer垂直扩缩这个容器。addon-resizer依据集群中节点的数量线性地扩展Metrics Server,以保证其能够有能力提供完整的metrics API服务。具体参考:链接其他基于Metrics Server的HPA:参考链接kubernetes的新监控体系中,metrics-server属于Core metrics(核心指标),提供API metrics.k8s.io,仅提供Node和Pod的CPU和内存使用情况。而其他Custom Metrics(自定义指标)由Prometheus等组件来完成,后续文章将对自定义指标进行解析。本文为容器监控实践系列文章,完整内容见: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

Docker查看远端仓库的标签工具

背景最近入坑了docker,比如本地想要启动一个elastic容器的话,直接通过以下命令即可快速启动一个elasticsearch的实例。docker run -d -p 9200:9200 \ -p 9300:9300 \ –name elasticsearch001 -h elasticsearch001 \ -e cluster.name=lookout-es \ -e ES_JAVA_OPTS="-Xms512m -Xmx512m" \ -e xpack.security.enabled=false \ elasticsearch/elasticsearch执行docker run命令最后一个参数是镜像名称,一般来说镜像命名遵循Registry/Repository/Image:tag规则,各部分含义如下Registry:公司统一的Docker Registry地址。Repository:镜像仓库,用来管理一类镜像。Image:具体某镜像的名称。tag:具体某镜像的标签。当我们执行上面的命令的时候,实际上会到默认的Registry(docker hub)上去拉取Repository名为elasticsearch且Image名为elasticsearch的镜像,镜像可能会存在多个版本的tag,默认情况下会拉取tag为latest的镜像。这里Registry/Repository/Image的问题不大,都比较好找,但是一般情况下镜像存在哪些版本用户比较难找,之前笔者就是通过到dockerhub上,一页一页的翻看所有的tag,这种情况效率比较低。后来笔者在[How to list all tags for a Docker image on a remote registry?](https://stackoverflow.com/que…,基本思路就是用docker官方提供的API接口对指定镜像进行查询,对接口数据进行处理后即可得到所有的tag,笔者觉得写的比较有意思,就拿来分析一下,中间过程需要用到sed、awk等相关知识。dockertags.sh代码如下:#!/bin/bashfunction usage() {cat << HELPdockertags – list all tags for a Docker image on a remote registry.EXAMPLE: - list all tags for ubuntu: dockertags ubuntu - list all php tags containing apache: dockertags php apacheHELP}if [ $# -lt 1 ]; then usage exitfiimage="$1"tags=wget -q https://registry.hub.docker.com/v1/repositories/${image}/tags -O - | sed -e 's/[][]//g' -e 's/"//g' -e 's/ //g' | tr '}' '\n' | awk -F: '{print $3}'if [ -n “$2” ]; then tags=echo "${tags}" | grep "$2"fiecho “${tags}“使用方式如下:dockertags ubuntu : 列出ubuntu镜像的所有tagdockertags php apache : 列出所有包含apache的php镜像的tag实现分析通过$# -lt 1判断shell的参数是否少于一个($#表示shell的参数个数),如果少于一个就执行usage函数,输出一些帮助信息并退出程序。如果大于等于一个参数则继续执行。$1: 表示shell中第1个参数,dockertags ubuntu中$1就是ubuntuwget -q https://registry.hub.docker.com/v1/repositories/${image}/tags -O -: 会将镜像名称拼接到查询的API接口中,形成https://registry.hub.docker.c…,通过wget访问该接口得到查询结果,-q参数会关闭wget冗余的输出,-O -参数让wget访问的结果可以在命令行中呈现,如下:接着通过sed来对得到json进行处理,sed -e 表示执行脚本,后面可以跟多个-e参数,每部分的解释如下:-e ’s/[][]//g’ :表示将json结果中前后的中括号去掉-e ’s/”//g’ :表示将json结果中的双引号去掉-e ’s/ //g’:表示将json中的空格去掉sed处理后的结果如下:sed处理完后,通过tr将json的右大括号替换成换行符,结果如下:最后通过awk指定通过-F参数指定各个字段分隔符为:将每行数据分隔成三个部分:’{print $3}‘直接输出第三列结果即为我们需要的镜像的tag列表,如下:此时tags变量中已经保存了所有与当前镜像相关的tag列表了,如果shell中的第二个参数不为空,就表示需要进一步的根据第二个参数进行过滤,比如dockertags php apache,此时$2就是apache,我们需要过滤出php镜像所有的tag中包含apache的tag,直接通过管道加上grep即可,echo “${tags}” | grep “$2” ,先做变量替换,在执行命令。最后输出所有满足条件的tag列表。 ...

January 12, 2019 · 1 min · jiezi

一个适合初级 Gopher 练手的项目

本项目见GITHUB:market_monitor这是一个初级 Gopher 练手的小项目;该项目功能简单,主要实现监测币市行情变化、达到预警效果的功能,大致的使用场景如下:用户登录服务;用户设置关注的币种及预警的走势价格;当行情变化触发到用户的预警设置时,服务将自动发送提醒邮件通知用户;整体功能简明,通过这个项目你可以了解到:基于 Go Mod 的项目包管理Gin 框架的基本开发Gin 服务跨域问题的处理基于 JWT 注册、登录等验证流程数据库 MySQL ORM 的基本使用缓存数据库 Redis 的基本使用为项目工程添加配置文件在项目中添加日志基于 Cron 的 Scheduler 定时任务如何为编写的 API 添加 Swagger 接口文档如何使应用服务平滑重启构建应用服务 Docker 镜像Docker Compose 容器编排部署功能点:常规的用户注册、登录、登出、身份验证等功能监测数据源的选择(用于选择支持监测的交易所数据源,目前仅支持监测 gate.io )监测策略设置管理(用于设置监测的具体规则要求,当前仅支持走势大小值预警策略)监测信息通知(在监测条件被触发后,发送消息通知用户,目前仅支持邮件通知)本项目用到的依赖:web framework: ginredis: redigomysql: gormlogger: zerologscheduler: cronconfig: viperjson web token: jwt-goswagger docs: swaggo最后,如果本项目能够对你有所帮助,请为本项目添加 star,非常感谢 ^_^

January 12, 2019 · 1 min · jiezi

k8s1.12.3集群使用token访问api

1.开启相关参数KUBE_API_ARGS="–service-node-port-range=30000-32767 –enable-swagger-ui=true –apiserver-count=3 –audit-log-maxage=30 –audit-log-maxbackup=3 –audit-log-maxsize=100 –audit-log-path=/var/log/k8s/audit.log –event-ttl=1h"2.创建用户,给cluster—admin角色,执行(kubectl create -f “yaml文件名”)apiVersion: v1kind: ServiceAccountmetadata: name: admin namespace: kube-system—kind: ClusterRoleBindingapiVersion: rbac.authorization.k8s.io/v1metadata: name: ecdataapisubjects: - kind: ServiceAccount name: ecdataapi namespace: kube-systemroleRef: kind: ClusterRole name: cluster-admin apiGroup: rbac.authorization.k8s.io3.获取tokenkubectl get secret -n kube-system|grep adminkubectl describe secret “上条命令执行结果(例:ecdataapi-token-9w7zj)” -n kube-system4. 拿到token后,postman(6.7.1)中file>settings>General>ssl certficate verification关闭5.获取服务地址kubectl cluster-info获取地址后复制地址到postman,将刚才生成的token复制到Authorization>type>bearer-Token6.访问目标地址

January 12, 2019 · 1 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

Docker Daemon生产环境关键的几个参数

原文地址一些docker daemon生产环境中要注意的参数配置。本文介绍一些生产环境中dockerd要特别注意的参数,这些参数可以通过在dockerd命令行参数形式给,也可以通过在/etc/docker/daemon.json里配置。本文介绍的就是daemon.json配置方式。在开始之前,请先查看/etc/docker/daemon.json是否存在,如果不存在则新建一个,内容是{}。然后你要懂JSON文件格式。如何应用配置下面所讲的配置最好在Docker安装完之后马上做,如果已经有容器运行了,那么先stop掉所有容器,然后再做。修改完之后重启Docker daemon,比如在Ubuntu 16.04下:sudo systemctl restart docker.service。然后执行docker info来验证配置是否生效。registry-mirrors{ “registry-mirrors”: []}此参数配置的是Docker registry的镜像网站,国内访问docker hub有时候会抽风,所以配置一个国内的镜像网站能够加速Docker image的下载。可以使用Daocloud加速器(需注册,使用免费)或者其他云厂商提供的免费的加速服务。它们的原理就是修改registry-mirrors参数。dns{ “dns”: []}Docker内置了一个DNS Server,它用来做两件事情:解析docker network里的容器或Service的IP地址把解析不了的交给外部DNS Server解析(dns参数设定的地址)默认情况下,dns参数值为Google DNS nameserver:8.8.8.8和8.8.4.4。我们得改成国内的DNS地址,比如:1.2.4.8阿里DNS:223.5.5.5和223.6.6.6114DNS:114.114.114.114和114.114.115.115比如:{ “dns”: [“223.5.5.5”, “223.6.6.6”]}log-driverLog driver是Docker用来接收来自容器内部stdout/stderr的日志的模块,Docker默认的log driver是JSON File logging driver。这里只讲json-file的配置,其他的请查阅相关文档。json-file会将容器日志存储在docker host machine的/var/lib/docker/containers/<container id>/<container id>-json.log(需要root权限才能够读),既然日志是存在磁盘上的,那么就要磁盘消耗的问题。下面介绍两个关键参数:max-size,单个日志文件最大尺寸,当日志文件超过此尺寸时会滚动,即不再往这个文件里写,而是写到一个新的文件里。默认值是-1,代表无限。max-files,最多保留多少个日志文件。默认值是1。根据服务器的硬盘尺寸设定合理大小,比如:{ “log-driver”: “json-file”, “log-opts”: { “max-size”: “100m”, “max-files”:“5” }}storage-driverDocker推荐使用overlay2作为Storage driver。你可以通过docker info | grep Storage来确认一下当前使用的是什么:$ docker info | grep ‘Storage’Storage Driver: overlay2如果结果不是overlay2,那你就需要配置一下了:{ “storage-driver”: “overlay2”}mtu如果docker host machine的网卡MTU为1500,则不需要此步骤MTU是一个很容易被忽略的参数,Docker默认的MTU是1500,这也是大多数网卡的MTU值。但是!在虚拟化环境下,docker host machine网卡的MTU可能不是1500,比如在openstack创建的虚拟的网卡的MTU是1450:$ ip link1: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000 link/ether fa:16:3e:71:09:f5 brd ff:ff:ff:ff:ff:ff当Docker网络的MTU比docker host machine网卡MTU大的时候可能会发生:容器外出通信失败影响网络性能所以将Docker网络MTU设置成和host machine网卡保持一致就行了,比如:{ “mtu”: 1450}验证:$ ip link3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default link/ether 02:42:6b🇩🇪95:71 brd ff:ff:ff:ff:ff:ff注意到docker0的MTU还是1500,不用惊慌,创建一个容器再观察就变成1450了(下面的veth是容器的虚拟网卡设备):$ docker run -itd –name busybox –rm busybox$ ip link3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP mode DEFAULT group default link/ether 02:42:6b🇩🇪95:71 brd ff:ff:ff:ff:ff:ff268: vethdf32b1b@if267: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master docker0 state UP mode DEFAULT group default link/ether 1a:d3:8a:3e:d3:dd brd ff:ff:ff:ff:ff:ff link-netnsid 2在到容器里看看它的网卡,MTU也是1450:$ docker exec busybox ip link267: eth0@if268: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff关于Overlay network的MTU看这篇文章参考资料Daemon CLIConfigure logging driversJSON File logging driverUse the OverlayFS storage driver ...

January 11, 2019 · 1 min · jiezi

Docker Overlay网络的MTU

原文地址Docker Daemon生产环境配置提到了MTU设置,但是这只是针对于名为bridge的docker bridge network,对于overlay network是无效的。如果docker host machine的网卡MTU为1500,则不需要此步骤设置ingress和docker_gwbridge的MTU以下步骤得在swarm init或join之前做假设你有三个机器,manager、worker-1、worker-2,准备搞一个Docker swarm集群1) [manager] docker swarm init2) [manager] 获得docker_gwbridge的参数,注意Subnet$ docker network inspect docker_gwbridge[ { “Name”: “docker_gwbridge”, … “IPAM”: { … “Config”: [ { “Subnet”: “172.18.0.0/16”, … } ] }, … }]3) [manager] docker swarm leave –force4) [manager] 停掉docker sudo systemctl stop docker.service5) [manager] 删掉虚拟网卡docker_gwbridge$ sudo ip link set docker_gwbridge down$ sudo ip link del dev docker_gwbridge6) [manager] 启动docker sudo systemctl start docker.service7) [manager] 重建docker_gwbridge,记得设置之前得到的Subnet参数和正确的MTU值$ docker network rm docker_gwbridge$ docker network create \ –subnet 172.18.0.0/16 \ –opt com.docker.network.bridge.name=docker_gwbridge \ –opt com.docker.network.bridge.enable_icc=false \ –opt com.docker.network.bridge.enable_ip_masquerade=true \ –opt com.docker.network.driver.mtu=1450 \ docker_gwbridge再到worker-1和worker-2上执行相同的命令。8) [manager] docker swarm init9) [manager] 先观察ingress network的参数,注意Subnet和Gateway:$ docker network inspect ingress[ { “Name”: “ingress”, … “IPAM”: { … “Config”: [ { “Subnet”: “10.255.0.0/16”, “Gateway”: “10.255.0.1” } ] }, … }]10) [manager] 删除ingress network,docker network rm ingress。11) [manager] 重新创建ingress network,记得填写之前得到的Subnet和Gateway,以及正确的MTU值:$ docker network create \ –driver overlay \ –ingress \ –subnet=10.255.0.0/16 \ –gateway=10.255.0.1 \ –opt com.docker.network.driver.mtu=1450 \ ingress12) [worker-1] [worker-2] join docker swarm join …注意:新机器在join到swarm之前,得先执行第7步验证:1) 启动一个swarm service,docker service create -td –name busybox busybox2) 观察虚拟网卡发现MTU都是1450:$ ip link1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:002: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000 link/ether fa:16:3e:71:09:f5 brd ff:ff:ff:ff:ff:ff3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP mode DEFAULT group default link/ether 02:42:6b🇩🇪95:71 brd ff:ff:ff:ff:ff:ff298: docker_gwbridge: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP mode DEFAULT group default link/ether 02:42:ae:7b💿b4 brd ff:ff:ff:ff:ff:ff309: veth7e0f9e5@if308: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master docker_gwbridge state UP mode DEFAULT group default link/ether 16:ca:8f:c7:d3:7f brd ff:ff:ff:ff:ff:ff link-netnsid 1311: vethcb94fec@if310: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master docker0 state UP mode DEFAULT group default link/ether 9a:aa🇩🇪7b:4f:d4 brd ff:ff:ff:ff:ff:ff link-netnsid 23) 观察容器内网卡网卡MTU也是1450:$ docker exec b.1.pdsdgghzyy5rhqkk5et59qa3o ip link1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00310: eth0@if311: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff自建overlay network的MTU方法一:在docker compose file设置…networks: my-overlay: driver: bridge driver_opts: com.docker.network.driver.mtu: 1450不过这样不好,因为这样就把docker compose file的内容和生产环境绑定了,换了个环境这个MTU值未必合适。方法二:外部创建时设置docker network create \ -d overlay \ –opt com.docker.network.driver.mtu=1450 \ –attachable \ my-overlay用法:在docker compose file里这样用:…networks: app-net: external: true name: my-overlaydocker run –network my-overlay …docker service create –network my-overlay …参考资料Use overlay networksDocker MTU issues and solutionsdocker network create ...

January 11, 2019 · 2 min · jiezi

安装Docker

用设置 Docker 的镜像仓库并从中进行安装前期准备更新包 $ sudo apt-get update安装软件包,以允许 apt 通过 HTTPS 使用镜像仓库: $ sudo apt-get install \ apt-transport-https \ ca-certificates \ curl \ software-properties-common添加 Docker 的官方 GPG 密钥: $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -验证密钥是否为:9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88 $ sudo apt-key fingerprint 0EBFCD88设置 stable 镜像仓库,不同的处理器架构不同,详情参见官网,我使用的:$ sudo add-apt-repository \ “deb [arch=amd64] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) \ stable"开始安装更新包 $ sudo apt-get update安装最新版本的 Docker CE $ sudo apt-get install docker-ce安装特定版本的 Docker CE输出可用版本 $ apt-cache madison docker-ce选择特定版本VERSION为版本号 $ sudo apt-get install docker-ce=<VERSION>检验安装sudo docker run hello-world输出一下信息为正常:Hello from Docker!This message shows that your installation appears to be working correctly.To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the “hello-world” image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal.To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bashShare images, automate workflows, and more with a free Docker ID: https://hub.docker.com/For more examples and ideas, visit: https://docs.docker.com/get-started/ ...

January 11, 2019 · 1 min · jiezi

Mac上用docker搭建Neo私链并调试

用虚拟机搭建私链的问题上一篇Neo私链中使用四台阿里云的windows搭建了私链,看到了网络建立的过程,但这样子有很多问题。阿里云跑了一天花了100块,费钱。手动搭建,下次又要重来。我用的mac电脑,跑windows虚拟机很费劲。总之,如果可以脚本化,自动化就更好了。所以总结一下用docker搭建私链。另外我的目的是研究代码,而研究代码光看不行,还要深入细节,如果可以调试就好了。调试一般分为两种,最简单的是看log,这种方法其实已经很无敌了,加上时间可以看性能,时序相关的问题;输出到文件可以调查长期运行后发生的错误。不过作为一个工具控,我还是要找到打断点调试的方法,????,说白了,打断点比在源码中插入很多log要方便点,还是人懒。创建私链并打断点调试的方案现在有以下几个方案,我都试验了,因为在mac上搞Neo,花了不少时间:mac上用docker跑一个私链,用vs code远程调试。mac上用docker跑一个私链,自己再开一个debug节点,调试本节点。在windows虚拟机里面用docker跑一个私链,自己在再一个debug节点,调试。结论:方法1:在mac中用vs code远程调试总是失败,不能获取调试进程,获取到了也调试不了,浪费了一天时间检查各种问题,最终放弃。方法2:在mac上用docker跑私链是没问题,但是mac上调试neo-cli。。。会崩溃。。。原因是需要leveldb,虽然mac可以用brew安装leveldb,但是好像要适配一下,我就不趟这水了,所以直接再windows上打开neo-cli,接上私链调试。方法3:windows上安装docker也是有条件的:Docker for Windows requires Windows 10 Pro or Enterprise version 10586, or Windows server 2016 RTM to run。不巧的是我的windows虚拟机是家庭版,就不折腾了。上面装个neo-gui玩玩得了。所以,最后的结论是:在mac上用docker跑了一个私链,然后在windows虚拟机上调试和玩Neo-gui。反正能用就好,其实最好可以全都在windows或者linux环境下玩,像我这样插一个mac就是有点麻烦了。废话少说,下面看看如何用docker建立私链并调试吧。使用docker创建私链这个在Neo的一个国外社区CityOfZion中已经有人做了,clone下来按照步骤一步一步来就好CityOfZion/neo-privatenet-dockerdocker的使用方法,这里有一本好书docker_practice,可以免费看,作者也是个大牛,也做区块链,有兴趣的可以看看他的项目。所需要的命令:git clone https://github.com/CityOfZion/neo-privatenet-docker.gitcd neo-privatenet-docker./docker_build.sh./docker_run_and_create_wallet.sh当执行成功后,就会产生一个私钥,里面就有提取好的Neo,可惜是私链的,不然就发财了。看下图就是成功后的打印。提取Neo和Gas成功在Neo-gui里面查看提取的Neo我们切回windows(注意虚拟机要是桥接模式),打开Neo-gui,1.注意copy正确的protocol.json到neo-gui的所在的目录注意下图红框,设置正确的公钥和ip地址 image.png先创建一个无用的钱包右击该钱包导入WIF再该钱包右击,导入WIF,用户体验的设计很奇怪4.看到提取的NEO和NeoGas 成功导入第二步就是为了可以右击导入,neo-gui没有别的入口提供导入功能调试下载neo-cli的代码切换到和docker一样的版本,我这边是v2.6.0copy libleveldb到neo-gui的目录copy protocol.jsoncopy config.json,注意修改一下端口,不要和docker中的节点使用一样的注意虚拟机要是桥接模式可以调试了总结后面我们可以方便的玩代码了,深入代码细节,可以学到很多东西,有点兴奋。参考资料CityOfZion/neo-privatenet-dockerDebugging .Net Core apps inside Docker container with VSCodevs code: Attaching to remote processesnodejs : LIVE DEBUGGING WITH DOCKERDebug .NET Core with docker containers #130vs code taskGuide: How to Import Private Key to NEO GUI作者:沈寅链接:https://www.jianshu.com/p/cd7…來源:简书简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

January 10, 2019 · 1 min · jiezi

使用Docker 一键部署 LNMP+Redis 环境

使用Docker 部署 LNMP+Redis 环境Docker 简介Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。推荐内核版本3.8及以上为什么使用Docker加速本地的开发和构建流程,容器可以在开发环境构建,然后轻松地提交到测试环境,并最终进入生产环境能够在让独立的服务或应用程序在不同的环境中得到相同的运行结果创建隔离的环境来进行测试高性能、超大规划的宿主机部署从头编译或者扩展现有的OpenShift或Cloud Foundry平台来搭建自己的PaaS环境目录安装Docker目录结构快速使用进入容器内部PHP扩展安装Composer安装常见问题处理常用命令Dockerfile语法docker-compose语法说明项目源码地址:GitHub安装Dockerwindows 安装参考macdocker toolbox参考 linux# 下载安装curl -sSL https://get.docker.com/ | sh# 设置开机自启sudo systemctl enable docker.servicesudo service docker start|restart|stop# 安装docker-composecurl -L https://github.com/docker/compose/releases/download/1.23.2/docker-compose-`uname -s-uname -m&gt; /usr/local/bin/docker-composechmod +x /usr/local/bin/docker-compose目录结构docker_lnmp├── v2├── mysql│ └── Dockerfile│ └── my.cnf├── nginx│ ├── Dockerfile│ ├── nginx.conf│ ├── log│ │ └── error.log│ └── www│ ├── index.html│ ├── index.php│ ├── db.php│ └── redis.php├── php│ ├── Dockerfile│ ├── www.conf│ ├── php-fpm.conf│ ├── php.ini│ └── log│ └── php-fpm.log└── redis └── Dockerfile └── redis.conf创建镜像与安装直接使用docker-compose一键制作镜像并启动容器版本一该版本是通过拉取纯净的CentOS镜像,通过Dockerfile相关命令进行源码编译安装各个服务。所以该方式很方便定制自己需要的镜像,但是占用空间大且构建慢。git clone https://github.com/voocel/docker-lnmp.gitcd docker-lnmpdocker-compose up -d版本二(推荐)git clone https://github.com/voocel/docker-lnmp.gitcd docker-lnmp/v2chmod 777 ./redis/redis.logchmod -R 777 ./redis/datadocker-compose up -d该版本是通过拉取官方已经制作好的各个服务的镜像,再通过Dockerfile相关命令根据自身需求做相应的调整。所以该方式构建迅速使用方便,因为是基于Alpine Linux所以占用空间很小。测试使用docker ps查看容器启动状态,若全部正常启动了则通过访问127.0.0.1、127.0.0.1/index.php、127.0.0.1/db.php、127.0.0.1/redis.php 即可完成测试(若想使用https则请修改nginx下的dockerfile,和nginx.conf按提示去掉注释即可,灵需要在ssl文件夹中加入自己的证书文件,本项目自带的是空的,需要自己替换,保持文件名一致)进入容器内部使用 docker execdocker exec -it ngixn /bin/sh使用nsenter命令# cd /tmp; curl https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.tar.gz | tar -zxf-; cd util-linux-2.24;# ./configure --without-ncurses# make nsenter &amp;&amp; sudo cp nsenter /usr/local/bin为了连接到容器,你还需要找到容器的第一个进程的 PID,可以通过下面的命令获取再执行。PID=$(docker inspect --format "{{ .State.Pid }}" container_id)# nsenter --target $PID --mount --uts --ipc --net --pidPHP扩展安装安装PHP官方源码包里的扩展(如:同时安装pdo_mysql mysqli pcntl gd四个个扩展)在php的Dockerfile中加入以下命令RUN apk add libpng-dev \ &amp;&amp; docker-php-ext-install pdo_mysql mysqli pcntl gd \注:因为该镜像缺少gd库所需的libpng-dev包,所以需要先下载这个包PECL 扩展安装# 安装扩展RUN pecl install memcached-2.2.0 \ # 启用扩展 &amp;&amp; docker-php-ext-enable memcached \通过下载扩展源码,编译安装的方式安装# 安装Redis和swoole扩展RUN cd ~ \ &amp;&amp; wget https://github.com/phpredis/phpredis/archive/4.2.0.tar.gz \ &amp;&amp; tar -zxvf 4.2.0.tar.gz \ &amp;&amp; mkdir -p /usr/src/php/ext \ &amp;&amp; mv phpredis-4.2.0 /usr/src/php/ext/redis \ &amp;&amp; docker-php-ext-install redis \ &amp;&amp; apk add libstdc++\ &amp;&amp; cd ~ \ &amp;&amp; wget https://github.com/swoole/swoole-src/archive/v4.2.12.tar.gz \ &amp;&amp; tar -zxvf v4.2.12.tar.gz \ &amp;&amp; mkdir -p /usr/src/php/ext \ &amp;&amp; mv swoole-src-4.2.12 /usr/src/php/ext/swoole \ &amp;&amp; docker-php-ext-install swoole \注:因为该镜像需要先安装swoole依赖的libstdc++,否则安装成功后无法正常加载swoole扩展Composer安装在Dockerfile中加入RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \常见问题处理redis启动失败问题在v2版本中redis的启动用户为redis不是root,所以在宿主机中挂载的./redis/redis.log和./redis/data需要有写入权限。 chmod 777 ./redis/redis.log chmod 777 ./redis/dataMYSQL连接失败问题在v2版本中是最新的MySQL8,而该版本的密码认证方式为Caching_sha2_password,而低版本的php和mysql可视化工具可能不支持,可通过phpinfo里的mysqlnd的Loaded plugins查看是否支持该认证方式,否则需要修改为原来的认证方式mysql_native_password: select user,host,plugin,authentication_string from mysql.user; ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456'; FLUSH PRIVILEGES;注意挂载目录的权限问题,不然容器成功启动几秒后立刻关闭,例:以下/data/run/mysql 目录没权限的情况下就会出现刚才那种情况docker run --name mysql57 -d -p 3306:3306 -v /data/mysql:/var/lib/mysql -v /data/logs/mysql:/var/log/mysql -v /data/run/mysql:/var/run/mysqld -e MYSQL_ROOT_PASSWORD=123456 -it centos/mysql:v5.7需要注意php.ini 中的目录对应 mysql 的配置的目录需要挂载才能获取文件内容,不然php连接mysql失败# php.inimysql.default_socket = /data/run/mysql/mysqld.sockmysqli.default_socket = /data/run/mysql/mysqld.sockpdo_mysql.default_socket = /data/run/mysql/mysqld.sock# mysqld.cnfpid-file = /var/run/mysqld/mysqld.pidsocket = /var/run/mysqld/mysqld.sock使用php连接不上redis# 错误的$redis = new Redis;$rs = $redis-&gt;connect('127.0.0.1', 6379);php连接不上,查看错误日志PHP Fatal error: Uncaught RedisException: Redis server went away in /www/index.php:7考虑到docker 之间的通信应该不可以用127.0.0.1 应该使用容器里面的ip,所以查看redis 容器的ip[root@localhost docker]# docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESb5f7dcecff4c docker_nginx "/usr/sbin/nginx -..." 4 seconds ago Up 3 seconds 0.0.0.0:80-&gt;80/tcp, 0.0.0.0:443-&gt;443/tcp nginx60fd2df36d0e docker_php "/usr/local/php/sb..." 7 seconds ago Up 5 seconds 9000/tcp php7c7df6f8eb91 hub.c.163.com/library/mysql:latest "docker-entrypoint..." 12 seconds ago Up 11 seconds 3306/tcp mysqla0ebd39f0f64 docker_redis "usr/local/redis/s..." 13 seconds ago Up 12 seconds 6379/tcp redis注意测试的时候连接地址需要容器的ip或者容器名names,比如redis、mysql.例如nginx配置php文件解析 fastcgi_pass php:9000;例如php连接redis $redis = new Redis;$res = $redis-&gt;connect('redis', 6379);因为容器ip是动态的,重启之后就会变化,所以可以创建静态ip第一步:创建自定义网络#备注:这里选取了172.172.0.0网段,也可以指定其他任意空闲的网段docker network create --subnet=172.171.0.0/16 docker-atdocker run --name redis326 --net docker-at --ip 172.171.0.20 -d -p 6379:6379 -v /data:/data -it centos/redis:v3.2.6连接redis 就可以配置对应的ip地址了,连接成功$redis = new Redis;$rs = $redis-&gt;connect('172.171.0.20', 6379);另外还有种可能phpredis连接不上redis,需要把redis.conf配置略作修改。bind 127.0.0.1改为:bind 0.0.0.0启动docker web服务时 虚拟机端口转发 外部无法访问 一般出现在yum update的时候(WARNING: IPv4 forwarding is disabled. Networking will not work.)或者宿主机可以访问,但外部无法访问vi /etc/sysctl.conf或者vi /usr/lib/sysctl.d/00-system.conf添加如下代码: net.ipv4.ip_forward=1重启network服务systemctl restart network查看是否修改成功sysctl net.ipv4.ip_forward如果返回为"net.ipv4.ip_forward = 1"则表示成功了如果使用最新的MySQL8无法正常连接,由于最新版本的密码加密方式改变,导致无法远程连接。# 修改密码加密方式ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';常用命令docker start 容器名(容器ID也可以)docker stop 容器名(容器ID也可以)docker run 命令加 -d 参数,docker 会将容器放到后台运行docker ps 正在运行的容器docker logs --tail 10 -tf 容器名 查看容器的日志文件,加-t是加上时间戳,f是跟踪某个容器的最新日志而不必读整个日志文件docker top 容器名 查看容器内部运行的进程docker exec -d 容器名 touch /etc/new_config_file 通过后台命令创建一个空文件docker run --restart=always --name 容器名 -d ubuntu /bin/sh -c "while true;do echo hello world; sleep 1; done" 无论退出代码是什么,docker都会自动重启容器,可以设置 --restart=on-failure:5 自动重启的次数docker inspect 容器名 对容器进行详细的检查,可以加 --format='{(.State.Running)}' 来获取指定的信息docker rm 容器ID 删除容器,注,运行中的容器无法删除docker rm $(docker ps -aq) 删除所有容器docker rmi $(docker images -aq) 删除所有镜像docker images 列出镜像docker pull 镜像名:标签 拉镜像docker search 查找docker Hub 上公共的可用镜像docker build -t='AT/web_server:v1' 命令后面可以直接加上github仓库的要目录下存在的Dockerfile文件。 命令是编写Dockerfile 之后使用的。-t选项为新镜像设置了仓库和名称:标签docker login 登陆到Docker Hub,个人认证信息将会保存到$HOME/.dockercfg,docker commit -m="comment " --author="AT" 容器ID 镜像的用户名/仓库名:标签 不推荐这种方法,推荐dockerfiledocker history 镜像ID 深入探求镜像是如何构建出来的docker port 镜像ID 端口 查看映射情况的容器的ID和容器的端口号,假设查询80端口对应的映射的端口run 运行一个容器, -p 8080:80 将容器内的80端口映射到docker宿主机的某一特定端口,将容器的80端口绑定到宿主机的8080端口,另 127.0.0.1:80:80 是将容器的80端口绑定到宿主机这个IP的80端口上,-P 是将容器内的80端口对本地的宿主机公开http://docs.docker.com/refere... 查看更多的命令docker push 镜像名 将镜像推送到 Docker Hubdocker rmi 镜像名 删除镜像docker attach 容器ID 进入容器docker network create --subnet=172.171.0.0/16 docker-at 选取172.172.0.0网段docker build 就可以加 -ip指定容器ip 172.171.0.10 了删除所有容器和镜像的命令docker rmdocker ps -a |awk ‘{print $1}’ | grep [0-9a-z]` 删除停止的容器docker rmi $(docker images | awk ‘/^<none>/ { print $3 }’)Dockerfile语法MAINTAINER 标识镜像的作者和联系方式EXPOSE 可以指定多个EXPOSE向外部公开多个端口,可以帮助多个容器链接FROM 指令指定一个已经存在的镜像#号代表注释RUN 运行命令,会在shell 里使用命令包装器 /bin/sh -c 来执行。如果是在一个不支持shell 的平台上运行或者不希望在shell 中运行,也可以 使用exec 格式 的RUN指令ENV REFRESHED_AT 环境变量 这个环境亦是用来表明镜像模板最后的更新时间VOLUME 容器添加卷。一个卷是可以 存在于一个或多个容器内的特定的目录,对卷的修改是立刻生效的,对卷的修改不会对更新镜像产品影响,例:VOLUME["/opt/project","/data"]ADD 将构建环境 下的文件 和目录复制到镜像 中。例 ADD nginx.conf /conf/nginx.conf 也可以是取url 的地址文件,如果是压缩包,ADD命令会自动解压、USER 指定镜像用那个USER 去运行COPY 是复制本地文件,而不会去做文件提取(解压包不会自动解压) 例:COPY conf.d/ /etc/apache2/ 将本地conf.d目录中的文件复制到/etc/apache2/目录中docker-compose.yml 语法说明image 指定为镜像名称或镜像ID。如果镜像不存在,Compose将尝试从互联网拉取这个镜像build 指定Dockerfile所在文件夹的路径。Compose将会利用他自动构建这个镜像,然后使用这个镜像command 覆盖容器启动后默认执行的命令links 链接到其他服务容器,使用服务名称(同时作为别名)或服务别名(SERVICE:ALIAS)都可以external_links 链接到docker-compose.yml外部的容器,甚至并非是Compose管理的容器。参数格式和links类似ports 暴露端口信息。宿主机器端口:容器端口(HOST:CONTAINER)格式或者仅仅指定容器的端口(宿主机器将会随机分配端口)都可以(注意:当使用 HOST:CONTAINER 格式来映射端口时,如果你使用的容器端口小于 60 你可能会得到错误得结果,因为 YAML 将会解析 xx:yy 这种数字格式为 60 进制。所以建议采用字符串格式。)expose 暴露端口,与posts不同的是expose只可以暴露端口而不能映射到主机,只供外部服务连接使用;仅可以指定内部端口为参数volumes 设置卷挂载的路径。可以设置宿主机路径:容器路径(host:container)或加上访问模式(host:container:ro)ro就是readonly的意思,只读模式volunes_from 挂载另一个服务或容器的所有数据卷environment 设置环境变量。可以属于数组或字典两种格式。如果只给定变量的名称则会自动加载它在Compose主机上的值,可以用来防止泄露不必要的数据env_file 从文件中获取环境变量,可以为单独的文件路径或列表。如果通过docker-compose -f FILE指定了模板文件,则env_file中路径会基于模板文件路径。如果有变量名称与environment指令冲突,则以后者为准(环境变量文件中每一行都必须有注释,支持#开头的注释行)extends 基于已有的服务进行服务扩展。例如我们已经有了一个webapp服务,模板文件为common.yml。编写一个新的 development.yml 文件,使用 common.yml 中的 webapp 服务进行扩展。后者会自动继承common.yml中的webapp服务及相关的环境变量net 设置网络模式。使用和docker client 的 –net 参数一样的值pid 和宿主机系统共享进程命名空间,打开该选项的容器可以相互通过进程id来访问和操作dns 配置DNS服务器。可以是一个值,也可以是一个列表cap_add,cap_drop 添加或放弃容器的Linux能力(Capability)dns_search 配置DNS搜索域。可以是一个值也可以是一个列表注意:使用compose对Docker容器进行编排管理时,需要编写docker-compose.yml文件,初次编写时,容易遇到一些比较低级的问题,导致执行docker-compose up时先解析yml文件的错误。比较常见的是yml对缩进的严格要求。yml文件还行后的缩进,不允许使用tab键字符,只能使用空格,而空格的数量也有要求,一般两个空格。项目源码地址:GitHub ...

January 10, 2019 · 3 min · jiezi

为什么需要Docker?

前言只有光头才能变强。文本已收录至我的GitHub仓库,欢迎Star:https://github.com/ZhongFuCheng3y/3y估计大家也可能听过Docker这项技术(在论坛上、招聘技能上、交流群上等等),要是不了解Docker,都不好意思在网上冲浪的时候吹牛逼了。所以这几天学了一下Docker,总结了Docker入门的相关知识,分享给大家(好让我们一起吹牛逼)。I need a doctor,call me a doctor. I need a doctor, doctor, to bring me back to life .一、为什么需要Docker官方介绍(中文版):http://www.docker-cn.com/what-docker#/developersDocker 是世界领先的软件容器平台。开发人员利用 Docker 可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用 Docker 可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用 Docker 可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为 Linux 和 Windows Server 应用发布新功能。1.1环境(切换/配置)麻烦一般我们写程序的,能接触到好几个环境:自己写代码的环境叫做开发环境。给测试去跑的环境叫做测试环境。测试完可以对外使用的叫做生产环境。其实我们在学习编程中,很多时间都浪费在“环境”上:如果我现在重装了系统,我想要跑我的war/jar包,我得去安装一下JDK、Tomcat、MySQL等配置各种的环境变量才能跑起来。开开心心地跟着博主给出的步骤去写Demo,但总是有Bug。(这里我将版本/依赖也归纳在环境的范畴里边)。好不容易在测试环境下跑起来了,在生产环境就各种出错!跟着教学视频做分布式/集群的项目,跑一堆的虚拟机,每个虚拟机都要安装对应的环境。所以就有个笑话《千万不要跟程序员说,你的代码有bug》:他的第一反应是你的环境有问题,第二就是你是傻逼不会用吧。你要跟他这么说:“这个程序运行的怎么运行的跟预期不一样,是我操作有问题吗?”。这货就会第一反应“我擦,这是不是出bug了?”1.2应用之间需要隔离比如我写了两个应用(网站),这两个应用部署在同一台服务器上,那可能会出现什么问题?如果一个应用出现了问题,导致CPU占100%。那另一个应用也会受到关联,跟着一起凉凉了。这两个应用是完全不同技术栈的应用,比如一个PHP,一个.NET。这两个应用各种的依赖软件都安装在同一个服务器上,可能就会造成各种冲突/无法兼容,这可能调试就非常麻烦了。二、Docker是如何解决上述的问题的2.1解决环境(切换/配置)不知道大家有没有装过系统,比如说装Linux虚拟机,重装Windows系统,都是需要镜像的。有了这个镜像,我们就可以运行这个镜像,来进行安装系统的操作(此处省略N个下一步),于是我们的系统就装好了。一般来说,我们去官方渠道下载的镜像,都是纯净的。比如去官方下载Windows镜像,装完后之后桌面只有一个回收站。但有过了解装系统的同学可能就会知道,有的镜像装完可能还有360这些软件,但系统的的确确是变了。简单来说,就是这些镜像添加了其他的东西(比如360软件、腾讯、千千静听等等软件)。Docker也是这种思路,可以将我们的想要的环境构建(打包)成一个镜像,然后我们可以推送(发布)到网上去。想要用这个环境的时候,在网上拉取一份就好了。有了Docker,我们在搭环境的时候,跟以前的方式就不一样了。之前:在开发环境构建出了一个war包,想跑到Linux下运行。我们得先在Linux下载好Java、Tomcat、MySQL,配置好对应的环境变量,将war包丢到Tomcat的webapps文件夹下,才能跑起来。现在:在Linux下直接拉取一份镜像(各种环境都配好了),将镜像运行起来,把war包丢进去就好了。将Docker的镜像运行起来就是一两秒的事情而已,十分方便的。2.2解决应用之间隔离说到这里,就得提出一个大家可能不认识的概念:LXC(Linux Containers)—>Linux容器。2.2.1Linux容器在Linux内核中,提供了cgroups功能,来达成资源的区隔化。它同时也提供了名称空间(namespace)区隔化的功能,使应用程序看到的操作系统环境被区隔成独立区间,包括进程树,网络,用户id,以及挂载的文件系统。简单来说就是:LXC是一个为Linux内核包含特征的用户接口。通过强大的API和简单的工具,它可以让Linux用户轻松的创建和托管系统或者应用程序容器。2.2.2回到Docker我们在翻看Docker的官方文档的时候,也很容易看见cgroup和namespace这两个名词:来源维基百科:Early versions of Docker used LXC as the container execution driver, though LXC was made optional in v0.9 and support was dropped in Docker v1.10.lxc是早期版本docker的一个基础组件,docker 主要用到了它对 Cgroup 和 Namespace 两个内核特性的控制。新的Docker版本已经移除了对LXC的support。2.2.3Docker在Windows和Mac上面说了,Docker底层用的Linux的cgroup和namespace这两项技术来实现应用隔离,那Windows和Mac用户能用Docker吗?之前,Windows和Mac使用Docker实际上就是跑了一层Linux虚拟机。比如在Windows下安装的是Docker Toolbox,它需要Oracle Virtual Box来跑Docker现在,Windows和Mac都已经原生支持Docker了。但需要一些安装的条件,详情可以查看官网比如Windows:Docker for Windows requires 64bit Windows 10 Pro and Microsoft Hyper-V参考资料:Windows 原生 Docker 正式商用http://blog.daocloud.io/windows-docker/三、虚拟机和Docker说到应用隔离和镜像,我就想起了虚拟机。今年下半年(此处省略…..),文体两开花(此处省略…..),要是我写文章写得不好,我是需要向XX谢罪的。估计大家都用过虚拟机,虚拟机也能实现对应用的隔离,安装特定的镜像也能跑出我们想要的环境。虚拟机已经发展了很久了,为什么我们还需要Docker呢?这部分内容在官网也有相关的介绍:http://www.docker-cn.com/what-container#/virtual_machines一句话总结:Docker容器比虚拟机轻量多了!最后Docker可以干嘛?将一整套环境打包封装成镜像,无需重复配置环境,解决环境带来的种种问题。Docker容器间是进程隔离的,谁也不会影响谁。其实这篇文章主要是讲为什么我们需要Docker(在学习一项技术之前,必须要知道这项技术是用来干嘛的),Docker的一些概念和命令我还没介绍(留到下一篇啦)。如果还没看过【生活现场】从搬家到容器技术docker应用场景解析,可以先去看看~我在学习Docker的时候也找到了不少的资源,想要获取Docker入门资源的同学可在公众号下回复“Docker”乐于分享和输出干货的Java技术公众号:Java3y。关注即可领取海量的视频资源!觉得我的文章写得不错,不妨点一下赞! ...

January 10, 2019 · 1 min · jiezi

Docker学习 P1

对着官方的英文教程和RUNOOB的简易教程,总算把心中想要的镜像搭出来了。做个笔记,怕忘记。Docker安装官方文档介绍了一堆步骤,检查有没有旧版本存在,有则要先删除旧版。我的CentOS因为是新安装,没有任何docker的文件夹,所以除旧的步骤跳过。安装Docker,安装前先安装必须的组件yum install -y yum-utils device-mapper-persistent-data lvm2yum install -y docker-ce启动Docker后台服务systemctl start docker测试是否安装妥当docker run hello-worldDocker一些常用命令容器相关操作#显示容器列表docker ps -a#启动/停止容器docker start/stop 容器ID/容器别名#修改容器别名docker rename 容器ID/容器别名 新别名#移除容器docker rm 容器ID/容器别名镜像相关操作#显示镜像清单docker images#删除镜像docker rmi 镜像ID日常操作#挂载镜像,并连接新容器docker run -i -t 镜像ID /bin/bash#连接容器docker attach 容器ID/容器别名#拉镜像docker pull 镜像名:TAGbuilddocker build -t 仓库名/镜像名:TAG Dockfile路径例如,我在DockerHub的仓库名是zhengsihan,镜像取名centos7_with_python3,TAG为1.0.0,则docker build -t zhengsihan/centos7_with_python3:1.0.0 .Dockfile路径,如果是在当前目录,则直接写点号(.)示例Dockfile:FROM centos:7.6.1810MAINTAINER Minus “zhengsihan.gz@outlook.com"RUN /bin/echo ‘root:123456’ |chpasswdRUN useradd zshRUN /bin/echo ‘zsh:123456’ |chpasswdRUN /bin/echo -e “LANG="en_US.UTF-8"” >/etc/default/localEXPOSE 22EXPOSE 80CMD /usr/sbin/sshd -D其他docker hub,用于搜索镜像资源,比docker search命令好用太多。链接:https://cloud.docker.com/实验用的Dockfile:https://github.com/zhengsihan…折腾了一个周末,最后build sucess真的开心。如果不是对操作系统有特殊要求,可以直接pull工具,nginx python jenkins这些热门的都有独立镜像,系统貌似是原版的Linux?

January 9, 2019 · 1 min · jiezi

一种生产环境Docker Overlay Network的配置方案

原文地址介绍一种生产环境Docker overlay network的配置方案。概要先讲一下生产环境中的问题:有多个Docker host,希望能够通过Docker swarm连接起来。Docker swarm只适合于无状态应用,不适合有状态应用。因此生产环境中会同时存在无状态应用:利用docker service create/docker stack deploy创建的。有状态应用:利用docker run/docker compose up创建的。希望两种应用能够连接到同一个overlay网络,在网络内部能够通过tasks.<service-name> DNS name 连接到无状态应用(见Container discovery)<container-name> DNS name 连接到有状态应用解决办法:创建attachable的overlay network有状态应用挂到这个overlay network上无状态应用也挂到这个overlay network上步骤到manager节点上创建attachable的overlay network,名字叫做prod-overlay:docker network create -d overlay –attachable prod-overlay在manager节点上查看这个网络是否创建成功:$ docker network lsNETWORK ID NAME DRIVER SCOPEfbfde97ed12a bridge bridge local73ab6bbac970 docker_gwbridge bridge locala2adb3de5f7a host host localnm7pgzuh6ww4 ingress overlay swarm638e550dab67 none null localqqf78g8iio10 prod-overlay overlay swarm在worker节点上查看这个网络,这时你看不到这个网络,不过不要担心,当后面在worker节点上创建工作负载后就能看到了:$ docker network lsNETWORK ID NAME DRIVER SCOPEfbfde97ed12a bridge bridge local73ab6bbac970 docker_gwbridge bridge locala2adb3de5f7a host host localnm7pgzuh6ww4 ingress overlay swarm638e550dab67 none null local在manager上创建容器c1,挂到prod-overlay network上:docker run –name c1 –network prod-overlay -itd busybox在worker上创建容器c2,挂到prod-overlay network上:docker run –name c2 –network prod-overlay -itd busybox在manager上创建service c,挂到prod-overlay network上:docker service create -td –name c –replicas 2 –network prod-overlay busybox验证查看worker节点的network之前在worker节点上没有看到prod-overlay network,现在你应该可以看见了:$ docker network lsNETWORK ID NAME DRIVER SCOPE01180b9d4833 bridge bridge localcd94df435afc docker_gwbridge bridge local74721e7670eb host host localnm7pgzuh6ww4 ingress overlay swarm32e6853ea78d none null localdw8kd2nb2yl3 prod-overlay overlay swarm确认容器可以互ping到manager节点上,让c1 ping c2$ docker exec c1 ping -c 2 c2PING c2 (10.0.2.2): 56 data bytes64 bytes from 10.0.2.2: seq=0 ttl=64 time=0.682 ms64 bytes from 10.0.2.2: seq=1 ttl=64 time=0.652 ms到manager节点上,让c1 ping tasks.c,tasks.c是之前创建的service c的DNS name:$ docker exec c1 ping -c 2 tasks.cPING tasks.c (10.0.2.8): 56 data bytes64 bytes from 10.0.2.8: seq=0 ttl=64 time=2.772 ms64 bytes from 10.0.2.8: seq=1 ttl=64 time=0.694 ms到manager节点上,让c1 查询 tasks.c的DNS name,可以看到tasks.c有两条记录:$ docker exec c1 nslookup -type=a tasks.cServer: 127.0.0.11Address: 127.0.0.11:53Non-authoritative answer:Name: tasks.cAddress: 10.0.2.7Name: tasks.cAddress: 10.0.2.8到manager节点上,查看service c的task,看到有c.1、c.2两个task,分别部署在两个节点上:$ docker service ps cID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTSp5n70vhtnz2f c.1 busybox:latest docker-learn-1 Running Running 17 minutes agobyuoox1t7cve c.2 busybox:latest docker-learn-2 Running Running 17 minutes ago到c.1 task所在的节点上,查看task c.1的容器名:$ docker ps -f name=c.1CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES795a3bd3c20a busybox:latest “sh” 21 minutes ago Up 21 minutes c.1.p5n70vhtnz2f5q8p2pcvbyfmw然后在c1里ping task c.1的容器名:$ docker exec c1 ping -c 2 c.1.p5n70vhtnz2f5q8p2pcvbyfmwPING c.1.p5n70vhtnz2f5q8p2pcvbyfmw (10.0.2.7): 56 data bytes64 bytes from 10.0.2.7: seq=0 ttl=64 time=0.198 ms64 bytes from 10.0.2.7: seq=1 ttl=64 time=0.128 ms你同样可以:在c2里:ping c1ping tasks.cping task c.1、c.2的容器在task c.1、c.2的容器里:ping c1、c2;ping tasks.cping task c.1、c.2的容器注意通过docker run / docker compose up创建的容器的名字,要保证在整个集群里是唯一的。docker 不会帮你检查名称冲突的情况,如果名称冲突了那么会得到错误的DNS结果。参考资料Use overlay networksUse an overlay network for standalone containers ...

January 9, 2019 · 2 min · jiezi

Prometehus自动发现Docker Swarm Overlay网络中的容器

原文地址介绍如何使用Prometheus的dns service discovery机制,自动发现并抓取Docker swarm overlay网络中的容器所提供的指标。使用docker service create/docker stack deploy能够很方便管理多个docker host,并且对应用做扩缩容。那么我们如何抓取这些动态创建的容器应用所提供的指标呢?在《使用Prometheus+Grafana监控JVM》一文里我们使用了static_config静态配置指标抓取目标,这显然在docker swarm环境里是不合适的。我们需要一种动态发现容器的方法。解决思路如下:使用《一种生产环境Docker Overlay Network的配置方案》提到的方法配置overlay网络,并且把docker service、stack、standalone container都挂到这个overlay网络里。把Prometheus也挂到这个overlay网络里。使用Prometheus的DNS service discovery机制,半自动的发现容器。本文所提到的脚本可以在这里下载下面构建一个实验环境以说明方法。第一步:构建overlay network根据《一种生产环境Docker Overlay Network的配置方案》里提到的方法,创建Docker swarm,和一个overlay网络,名字叫做test-overlay:docker network create -d overlay –attachable test-overlay第二步:启动容器为了方便起见,使用prometheus-mock-data来模拟一个提供指标的应用,这样就能够避免繁琐的jmx-exporter。1) 新建一个目录,名字叫做docker-swarm-demo2) 新建一个文件scrape-data.txt,这个文件就是我们要提供的假指标,内容如下:# HELP x mock metric# TYPE x gaugex 1—# HELP x mock metric# TYPE x gaugex 2—# HELP x mock metric# TYPE x gaugex 3—# HELP x mock metric# TYPE x gaugex 43) 为了演示docker service和standalone container都能被采集到,会启动这两种形式的容器:4) 使用docker service create启动一个service,replicas=3(注意–name参数):docker service create \ –name mock \ –replicas 3 \ –network test-overlay \ –limit-memory 96M \ –mount type=bind,src=$(pwd)/scrape-data.txt,dst=/home/java-app/etc/scrape-data.txt \ chanjarster/prometheus-mock-data:latest4) 使用docker run启动一个standalone container(注意–name参数):docker run -d \ -v $(pwd)/scrape-data.txt:/home/java-app/etc/scrape-data.txt \ –network test-overlay \ –name standalone-mock \ chanjarster/prometheus-mock-data:latest第三步:启动Prometheus1) 在之前新建目录docker-swarm-demo里创建文件prom-config.yml,内容如下:scrape_configs: - job_name: ‘swarm-service’ scrape_interval: 30s dns_sd_configs: - names: - tasks.mock - standalone-mock type: A port: 8080 relabel_configs: - source_labels: [’__meta_dns_name’] target_label: ‘service’注意到上面的两个关键配置:设定了两个DNS A记录,tasks.mock和standalone-mock。tasks.mock是Docker自动为docker service mock创建的,而standalone-mock就是容器名。文章最开始说到的半自动就是这个意思,我们得事先知道DNS A记录有哪些,然后让Prometheus去发现这些DNS A记录背后对应的容器有哪些。把__meta_dns_name的值设置到指标的service 这个label里。2) 启动Prometheus:docker run -d \ –name=prometheus \ –network test-overlay \ -p 9090:9090 \ -v $(pwd):/prometheus-config \ -v $(pwd)/prom-data:/prometheus \ prom/prometheus –config.file=/prometheus-config/prom-config.yml3) 访问 http://localhost:9090 看看Prometheus是否启动成功,在输入框里输入x然后执行,应该可以看到如下图的结果:其中3个instance是属于tasks.mock的,还有一个则是standalone container(如果没有看到4个instance,那么等一会儿再试)。 ...

January 9, 2019 · 1 min · jiezi

容器网络通,但业务网络不通,怎么办?

本着为大家分享有用的技能和知识的原则,专门出了一个解决 bug 的主题分享。具体的 bug 描述可以看下面(亲身经验传授给你们)问题一描述:某线上业务有 A,B 两个集群,集群之间存在横向访问, 当 A 集群中的某个容器(A.a)重建(发布更新)之后发现,(A.a)可以 ping 通 B 集群中的(B.b)容器,而通过 curl 和 telnet 业务端口的时候是连接被拒绝;第一直觉是业务自身的问题,就在 B.b 容器里启动了 python 测试服务, 在 A.a 容器里通过 curl 测试也没通,那到底是不是业务自身的问题?该如何排查此类呢? 是不是 k8s 或 docker 的 bug 呢?问题二描述:大家都知道 etcd 作为 k8s 集群的核心数据存储系统,堪称是 k8s 的大脑,所以 etcd 的稳定级别要求不言而喻了,那么大家试想一下, 如果 etcd 里的数据假如被误删了,后果会怎么样呢?该如何做 etcd 的故障演练呢?通过故障演练又会引发出哪个 k8s api-server 的 bug 呢?如果你也有遇到类似的问题或者你想要知道面对这些问题时该如何下手解决?那就来听听我们明晚的分享课,为你揭晓详细的解决办法。分享讲师:GY 老师10年一线软件开发经验,先后经历了传统安全公司,以及多家互联网公司;在安全开发方面,曾开发过 Linux 防火墙、web 应用防火墙、Linux 安全内核加固,基于大流量的 Web 安全威胁分析等项目;在互联网公司工作时,曾基于 DPDK 高性能网络开发框架开发过基于全流量的网络流量分析平台和基于 Sflow 网络流量分析平台,基于 Golang 开发 SmartDNS 等;开发语言也是从C -> python -> golang 的转变过程?现从事基于 K8S 和 Docker在私有云平台建设方面的研发工作;具备丰富的Linux系统开发经验、网络开发经验以及项目管理经验;目前开发工作 90+% 都在用 Golang,Golang 是一门简洁、高效、强大且灵活的编程语言。添加WeChat:17812796384 获取参与方式(倾听大佬给你普及bug的解决办法) ...

January 9, 2019 · 1 min · jiezi

解决 docker 容器无法通过 IP 访问宿主机问题

问题起源在使用 docker 的过程中我不幸需要在 docker 容器中访问宿主机的 80 端口, 而这个 80 端口是另外一个容器 8080 端口映射出去的. 当我在容器里通过 docker 的网桥 172.17.0.1 访问宿主机时, 居然发现:curl: (7) Failed to connect to 172.17.0.1 port 80: No route to host查找问题原因可以确定的是容器与宿主机是有网络连接的, 因为可以在容器内部通过 172.17.0.1 Ping 通宿主机:root@930d07576eef:/# ping 172.17.0.1PING 172.17.0.1 (172.17.0.1) 56(84) bytes of data.64 bytes from 172.17.0.1: icmp_seq=1 ttl=64 time=0.130 ms也可以在容器内部访问其它内网和外网.iptables 显示也允许 docker 容器访问:# iptables –list | grep DOCKERDOCKER-ISOLATION all – anywhere anywhere DOCKER all – anywhere anywhere Chain DOCKER (1 references)Chain DOCKER-ISOLATION (1 references)之后在查找一些资料后发现这个问题: NO ROUTE TO HOST network request from container to host-ip:port published from other container.解释正如 Docker Community Forms 所言, 这是一个已知的 Bug, 宿主机的 80 端口允许其它计算机访问, 但是不允许来自本机的 Docker 容器访问. 必须通过设置 firewalld 规则允许本机的 Docker 容器访问.gypark 指出可以通过在 /etc/firewalld/zones/public.xml 中添加防火墙规则避免这个问题:<rule family=“ipv4”> <source address=“172.17.0.0/16” /> <accept /></rule>注意这里的 172.17.0.0/16 可以匹配 172.17.xx.xx IP 段的所有 IP.之后重启下防火墙:systemctl restart firewalld之后就可以在 docker 容器内部访问宿主机 80 端口.其它问题实际上当我又用 vmware 新开了一台虚拟机希望能重现这个问题的时候, 发现在新的虚拟机上居然没有类似的问题. 也就是说容器可以直接通过172.17.0.1访问宿主机 80 端口, 查看防火墙配置也没看到有172.17.xx.xx的白名单.猜测是由于在新的虚拟机安装的 docker 是 Docker version 1.12.5, build 047e51b/1.12.5, 也就是 Red Hat 从 docker 开源版本迁出开发的版本, 而之前的是 Docker version 17.06.2-ce, build cec0b72 属于 Docker-CE, 可能是 docker 版本有差异, Red Hat 顺便把那个 Known Bug 修复了. ...

January 9, 2019 · 1 min · jiezi

Docker问题:Docker默认路径存储空间不足,迁移Docker默认存储目录

问题在docker使用用过程中出现,出现如下错误提示:ERROR: Service ‘XXX’ failed to build: write /var/lib/docker/tmp/GetImageBlob239544438: no space left on device原因错误提示已经很明显了,就是现在docker存储路径所在分区存储空间不足,一般都是默认路径/var/lib/dokcer,当然,这个原因引起的问题可能会有很多种,之前也也做到过,所以当docker忽然出现问题的时候,记得df -h看一下空闲的空间大小。解决方案此方案针对的系统环境和版本分别是:Contos 7和Dokcer 17.03.2-ce,其他环境下并不一定有效。关掉所有正在运行的容器docker stop $(docker ps -q -f status=running)关闭docker服务systemctl stop docker将Docker现目录挪到一个新目录下,这两个目录依照具体情况而定,我的分别是/var/lib/docker和/home/dockermv /var/lib/docker /home/docker 将原来的数据备份一份,备份大法好,万一不行还不至于损坏数据tar zcf docker_file_bak.tar.gz /home/docker修改服务启动命令,服务的service文件为/lib/systemd/system/docker.service,将里面的内容ExecStart=/usr/bin/dockerd修改为如下:ExecStart=/usr/bin/dockerd -g 新目录重新加载修改后的service文件systemctl daemon-reload启动Docker服务systemctl start docker验证修改成功docker info | grep “Docker Root Dir"可以看到结果已经是新的目录再次说明,该方案只在前面提到的环境下测试过,并不是所有的环境都试用,因为之前在ubuntu 14.04和ubuntu 16.04上尝试使用该方法都不能生效,后续再进行说明。

January 8, 2019 · 1 min · jiezi

Docker问题:Dockerfile的From之前不能使用ARG

问题在使用开源Dockerfile构建镜像时,dockerfile的From之前通过ARG定义版本,指定基础镜像,如下:ARG VERSION=lastedFROM alpine:${VERSION}执行docker build报错如下:Please provide a source image with from prior to commit原因允许这种用法是在docker 17.05.0-ce (2017-05-04)之后才引入的,查看本机版本为17.03.2-ce,所以报错很正常了,可以参见Allow using build-time args (ARG) in FROM #31352解决方案更新docker版本

January 8, 2019 · 1 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

etcd 集群运维实践

【编者的话】etcd 是 Kubernetes 集群的数据核心,最严重的情况是,当 etcd 出问题彻底无法恢复的时候,解决问题的办法可能只有重新搭建一个环境。因此围绕 etcd 相关的运维知识就比较重要,etcd 可以容器化部署,也可以在宿主机自行搭建,以下内容是通用的。集群的备份和恢复添加备份!/bin/bashIP=123.123.123.123BACKUP_DIR=/alauda/etcd_bak/mkdir -p $BACKUP_DIRexport ETCDCTL_API=3etcdctl –endpoints=http://$IP:2379 snapshot save $BACKUP/snap-$(date +%Y%m%d%H%M).db备份一个节点的数据就可以恢复,实践中,为了防止定时任务配置的节点异常没有生成备份,建议多加几个恢复集群!/bin/bash使用 etcdctl snapshot restore 生成各个节点的数据比较关键的变量是–data-dir 需要是实际 etcd 运行时的数据目录–name –initial-advertise-peer-urls 需要用各个节点的配置–initial-cluster initial-cluster-token 需要和原集群一致ETCD_1=10.1.0.5ETCD_2=10.1.0.6ETCD_3=10.1.0.7for i in ETCD_1 ETCD_2 ETCD_3doexport ETCDCTL_API=3etcdctl snapshot restore snapshot.db --data-dir=/var/lib/etcd --name $i --initial-cluster ${ETCD_1}=http://${ETCD_1}:2380,${ETCD_2}=http://${ETCD_2}:2380,${ETCD_3}=http://${ETCD_3}:2380 --initial-cluster-token k8s_etcd_token --initial-advertise-peer-urls http://$i:2380 && \mv /var/lib/etcd/ etcd_$idone把 etcd_10.1.0.5 复制到 10.1.0.5节点,覆盖/var/lib/etcd(同–data-dir路径)其他节点依次类推用 etcd 自动创建的 SnapDb 恢复!/bin/bashexport ETCDCTL_API=3etcdctl snapshot restore snapshot.db --skip-hash-check --data-dir=/var/lib/etcd --name 10.1.0.5 --initial-cluster 10.1.0.5=http://10.1.0.5:2380,10.1.0.6=http://10.1.0.6:2380,10.1.0.7=http://10.1.0.7:2380 --initial-cluster-token k8s_etcd_token --initial-advertise-peer-urls http://10.1.0.5:2380也是所有节点都需要生成自己的数据目录,参考上一条和上一条命令唯一的差别是多了 –skip-hash-check (跳过完整性校验)这种方式不能确保 100% 可恢复,建议还是自己加备份通常恢复后需要做一下数据压缩和碎片整理,可参考相应章节踩过的坑[ 3.0.14 版 etcd restore 功能不可用 ] https://github.com/etcd-io/et…使用更新的 etcd 即可。总结:恢复就是要拿 DB 去把 etcd 的数据生成一份,用同一个节点的,可以保证除了 restore 时候指定的参数外,所有数据都一样。这就是用一份 DB,操作三次(或者5次)的原因。集群的扩容——从 1 到 3执行添加!/bin/bashexport ETCDCTL_API=2etcdctl –endpoints=http://10.1.0.6:2379 member add 10.1.0.6 http://10.1.0.6:2380etcdctl –endpoints=http://10.1.0.7:2379 member add 10.1.0.7 http://10.1.0.7:2380ETCD_NAME=“etcd_10.1.0.6"ETCD_INITIAL_CLUSTER=“10.1.0.6=http://10.1.0.6:2380,10.1.0.5=http://10.1.0.5:2380"ETCD_INITIAL_CLUSTER_STATE=“existing"准备添加的节点 etcd 参数配置!/bin/bash/usr/local/bin/etcd –data-dir=/data.etcd –name 10.1.0.6–initial-advertise-peer-urls http://10.1.0.6:2380 –listen-peer-urls http://10.1.0.6:2380 –advertise-client-urls http://10.1.0.6:2379 –listen-client-urls http://10.1.0.6:2379 –initial-cluster 10.1.0.6=http://10.1.0.6:2380,10.1.0.5=http://10.1.0.5:2380–initial-cluster-state exsiting–initial-cluster-token k8s_etcd_token–initial-cluster 集群所有节点的 name=ip:peer_url–initial-cluster-state exsiting 告诉 etcd 自己归属一个已存在的集群,不要自立门户踩过的坑从 1 到 3 期间,会经过集群是两节点的状态,这时候可能集群的表现就像挂了,endpoint status 这些命令都不能用,所以我们需要用 member add 先把集群扩到三节点,然后再依次启动 etcd 实例,这样做就能确保 etcd 就是健康的。从 3 到更多,其实还是 member add 啦,就放心搞吧。集群加证书生成证书curl -s -L -o /usr/bin/cfssl https://pkg.cfssl.org/R1.2/cf...curl -s -L -o /usr/bin/cfssljson https://pkg.cfssl.org/R1.2/cf...chmod +x /usr/bin/{cfssl,cfssljson}cd /etc/kubernetes/pki/etcdcat ca-config.json{“signing”: {“default”: { “expiry”: “100000h”},“profiles”: { “server”: {“usages”: [“signing”, “key encipherment”, “server auth”, “client auth”],“expiry”: “100000h”}, “client”: {“usages”: [“signing”, “key encipherment”, “server auth”, “client auth”],“expiry”: “100000h”}}}}cat ca-csr.json{“CN”: “etcd”,“key”: {“algo”: “rsa”,“size”: 4096},“names”: [{ “C”: “CN”, “L”: “Beijing”, “O”: “Alauda”, “OU”: “PaaS”, “ST”: “Beijing”}]}cat server-csr.json{“CN”: “etcd-server”,“hosts”: [“localhost”,“0.0.0.0”,“127.0.0.1”,“所有master 节点ip “,“所有master 节点ip “,“所有master 节点ip “],“key”: {“algo”: “rsa”,“size”: 4096},“names”: [{ “C”: “CN”, “L”: “Beijing”, “O”: “Alauda”, “OU”: “PaaS”, “ST”: “Beijing”}]}cat client-csr.json{“CN”: “etcd-client”,“hosts”: [””],“key”: {“algo”: “rsa”,“size”: 4096},“names”: [{ “C”: “CN”, “L”: “Beijing”, “O”: “Alauda”, “OU”: “PaaS”, “ST”: “Beijing”}]} cd /etc/kubernetes/pki/etcdcfssl gencert -initca ca-csr.json | cfssljson -bare cacfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server server-csr.json | cfssljson -bare servercfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=client client-csr.json | cfssljson -bare client参考链接:https://lihaoquan.me/2017/3/29 … .html首先更新节点的peer-urlsexport ETCDCTL_API=3etcdctl –endpoints=http://x.x.x.x:2379 member list1111111111 ……….2222222222 ……….3333333333 ……….etcdctl –endpoints=http://172.30.0.123:2379 member update 1111111111 –peer-urls=https://x.x.x.x:2380执行三次把三个节点的peer-urls都改成https修改配置vim /etc/kubernetes/main*/etcd.yamletcd启动命令部分修改 http 为 https,启动状态改成 existing–advertise-client-urls=https://x.x.x.x:2379–initial-advertise-peer-urls=https://x.x.x.x:2380–initial-cluster=xxx=https://x.x.x.x:2380,xxx=https://x.x.x.x:2380,xxx=https://x.x.x.x:2380–listen-client-urls=https://x.x.x.x:2379–listen-peer-urls=https://x.x.x.x:2380–initial-cluster-state=existingetcd 启动命令部分插入–cert-file=/etc/kubernetes/pki/etcd/server.pem–key-file=/etc/kubernetes/pki/etcd/server-key.pem–peer-cert-file=/etc/kubernetes/pki/etcd/server.pem–peer-key-file=/etc/kubernetes/pki/etcd/server-key.pem–trusted-ca-file=/etc/kubernetes/pki/etcd/ca.pem–peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.pem–peer-client-cert-auth=true–client-cert-auth=true检索hostPath在其后插入hostPath:path: /etc/kubernetes/pki/etcdtype: DirectoryOrCreatename: etcd-certs检索mountPath在其后插入mountPath: /etc/kubernetes/pki/etcdname: etcd-certsvim /etc/kubernetes/main*/kube-apiserver.yamlapiserver 启动部分插入,修改 http 为https–etcd-cafile=/etc/kubernetes/pki/etcd/ca.pem–etcd-certfile=/etc/kubernetes/pki/etcd/client.pem–etcd-keyfile=/etc/kubernetes/pki/etcd/client-key.pem–etcd-servers=https://x.x.x.x:2379,https://x.x.x.x:2379,https://x.x.x.x:2379总结下就是,先准备一套证书。然后修改 etcd 内部通信地址为https,这时候etcd日志会报错(可以忽略),然后用etcd –带证书的参数启动,把所有链接etcd的地方都用上证书,即可。遇到的坑[ etcd 加证书后,apiserver 的健康检查还是 http 请求,etcd 会一直刷日志 ] https://github.com/etcd-io/et...2018-02-06 12:41:06.905234 I | embed: rejected connection from “127.0.0.1:35574” (error “EOF”, ServerName “")解决办法:直接去掉 apiserver 的健康检查,或者把默认的检查命令换成 curl(apiserver 的镜像里应该没有 curl,如果是刚需的话自己重新 build 一下吧)集群升级已经是 v3 的的集群不需要太多的配置,保留数据目录,替换镜像(或者二进制)即可;v2 到 v3 的升级需要一个 merge 的操作,我并没有实际的实践过,也不太推荐这样做。集群状态检查其实上述所有步骤都需要这些命令的辅助——!/bin/bash如果证书的话,去掉–cert –key –cacert 即可–endpoints= 需要写了几个节点的url,endpoint status就输出几条信息export ETCDCTL_API=3etcdctl --endpoints=https://x.x.x.x:2379 –cert=/etc/kubernetes/pki/etcd/client.pem --key=/etc/kubernetes/pki/etcd/client-key.pem --cacert=/etc/kubernetes/pki/etcd/ca.pem \endpoint status -w tableetcdctl –endpoints=xxxx endpoint healthetcdctl –endpoints=xxxx member listkubectl get cs数据操作(删除、压缩、碎片整理)删除ETCDCTL_API=2 etcdctl rm –recursive # v2 的 api 可以这样删除一个“目录”ETCDCTL_API=3 etcdctl –endpoints=xxx del /xxxxx –prefix # v3 的版本带证书的话,参考上一条添加 –cert –key –cacert 即可遇到的坑:在一个客户环境里发现 Kubernetes 集群里的 “事件” 超级多,就是 kubectl describe xxx 看到的 events 部分信息,数据太大导致 etcd 跑的很累,我们就用这样的方式删掉没用的这些数据。碎片整理ETCDCTL_API=3 etcdctl –endpoints=xx:xx,xx:xx,xx:xx defragETCDCTL_API=3 etcdctl –endpoints=xx:xx,xx:xx,xx:xx endpoint status # 看数据量压缩ETCDCTL_API=3 etcdctl –endpoints=xx:xx,xx:xx,xx:xx compact这个在只有 K8s 用的 etcd 集群里作用不太大,可能具体场景我没遇到可参考这个文档https://www.cnblogs.com/davyg…不过跑一下不碍事etcd –auto-compaction-retention=1添加这个参数让 etcd 运行时自己去做压缩常见问题etcd 对时间很依赖,所以集群里的节点时间一定要同步磁盘空间不足,如果磁盘是被 etcd 自己吃完了,就需要考虑压缩和删数据啦加证书后所有请求就都要带证书了,要不会提示 context deadline exceeded做各个操作时 etcd 启动参数里标明节点状态的要小心,否则需要重新做一遍前面的步骤很麻烦日志收集etcd 的日志暂时只支持 syslog 和 stdout 两种——https://github.com/etcd-io/et…etcd 的日志在排查故障时很有用,如果我们用宿主机来部署 etcd,日志可以通过 systemd 检索到,但 kubeadm 方式启动的 etcd 在容器重启后就会丢失所有历史。我们可以用以下的方案来做——shell 的重定向etcd –xxxx –xxxx > /var/log/etcd.log配合 logratate 来做日志切割将日志通过 volume 挂载到宿主机supervisorsupervisor 从容器刚开始流行时,就是保持服务持续运行很有效的工具。sidecar 容器(后续我在 GitHub 上补充一个例子,github.com/jing2uo)Sidecar 可以简单理解为一个 Pod 里有多个容器(比如 kubedns)他们彼此可以看到对方的进程,因此我们可以用传统的 strace 来捕捉 etcd 进程的输出,然后在 Sidecar 这个容器里和 shell 重定向一样操作。strace -e trace=write -s 200 -f -p 1Kubeadm 1.13 部署的集群最近我们测试 Kubernetes 1.13 集群时发现了一些有趣的改变,诈一看我们上面的命令就没法用了——https://kubernetes.io/docs/set … logy/区分了 Stacked etcd topology 和 External etcd topology,官方的链接了这个图很形象——B70D000C-83B2-4B9C-A612-53925238D0DA.png这种模式下的 etcd 集群,最明显的差别是容器内 etcd 的initial-cluster 启动参数只有自己的 IP,会有点懵挂了我这该怎么去恢复。其实基本原理没有变,Kubeadm 藏了个 ConfigMap,启动参数被放在了这里——kubectl get cm etcdcfg -n kube-system -o yamletcd: local:serverCertSANs:- “192.168.8.21"peerCertSANs:- “192.168.8.21"extraArgs: initial-cluster: 192.168.8.21=https://192.168.8.21:2380,192.168.8.22=https://192.168.8.22:2380,192.168.8.20=https://192.168.8.20:2380 initial-cluster-state: new name: 192.168.8.21 listen-peer-urls: https://192.168.8.21:2380 listen-client-urls: https://192.168.8.21:2379 advertise-client-urls: https://192.168.8.21:2379 initial-advertise-peer-urls: https://192.168.8.21:2380Q&AQ:请问 etcd 监控和告警如何做的?告警项都有哪些?A:告警要看用的什么监控吧,和 Kubernetes 配套比较常见的是普罗米修思和 Grafana 了。告警项我没有具体配过,可以关注的点是:endpoint status -w table 里可以看到数据量,endpoints health 看到健康状态,还有内存使用这些,具体可以参考普罗米修思的 exporter 是怎么做的。Q:使用 Kubeadm 部署高可用集群是不是相当于先部署三个独立的单点 Master,最后靠 etcd 添加节点操作把数据打通?A:不是,Kubeadm 部署会在最开始就先建一个 etcd 集群,apiserver 启动之前就需要准备好 etcd,否则 apiserver 起不了,集群之间就没法通信。可以尝试手动搭一下集群,不用 Kubeadm,一个个把组件开起来,之后对Kubernetes的组件关系会理解更好的。Q:etcd 跨机房高可用如何保证呢?管理 etcd 有好的 UI 工具推荐么?A:etcd 对时间和网络要求很高,所以跨机房的网络不好的话性能很差,光在那边选请输入链接描述举去了。我分享忘了提一个 etcd 的 mirror,可以去参考下做法。跨机房的话,我觉得高速网络是个前提吧,不过还没做过。UI 工具没找过,都是命令行操作来着。Q:Kubeadm 启动的集群内 etcd节 点,kubectl 操作 etcd 的备份恢复有尝试过吗?A:没有用 kubectl 去处理过 etcd 的备份恢复。etcd 的恢复依赖用 SnapDb 生成数据目录,把 etcd 进程丢进容器里,类似的操作避免不了,还有启动的状态需要修改。kubeadm 启动的 etcd 可以通过 kubectl 查询和 exec,但是数据操作应该不可以,比如恢复 etcd ing 时,无法连接 etcd,kubectl 还怎么工作?Q:kubeadm-ha 启动 3 个 Master,有 3 个 etcd 节点,怎么跟集群外的 3 个 etcd 做集群,做成 3 Master 6 etcd?A:可以参考文档里的扩容部分,只要保证 etcd 的参数正确,即使一个集群一部分容器化,一部分宿主机,都是可以的(当然不建议这么做)。可以先用 kubeadm 搭一个集群,然后用扩容的方式把其他三个节点加进来,或者在 kubeadm 操作之前,先搭一个 etcd 集群。然后 kubeadm 调用它就可以。Q:有没有试过 Kubeadm 的滚动升级,etcd 版本变更,各 Master 机分别重启,数据同步是否有异常等等?A:做过。Kubeadm 的滚动升级公司内部有从 1.7 一步步升级到 1.11、1.12 的文档,或多或少有一点小坑,不过今天主题是 etcd 所以没提这部分。各个 Master 分别重启后数据的一致我们测试时没问题,还有比较极端的是直接把三 Master 停机一天,再启动后也能恢复。以上内容根据2019年1月3日晚微信群分享内容整理。分享人郭靖,灵雀云运维开发工程师,有大规模集群运维经验,对自动化迷之热衷,精通Ansible,HashiCorp工具集,容器和Kubernetes鼓捣了三年,喜欢用Python和Go写小工具,DevOps推崇及践行者,近期关注和期待OpsMop。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。 ...

January 7, 2019 · 3 min · jiezi

docker学习日记-目录挂载方法

docker学习日记-目录挂载映射本地目录通过docker run的-v属性进行宿主机和目标容器的目录挂载docker run -d -v $PWD/html:/usr/share/nginx/html nginx通过先创建存储数据的容器,再将数据容器挂载到目标容器# 创建数据容器docker create -v $PWD/data:/var/mydata –name data_container centos# 创建目标容器并挂载数据容器docker run -d –volumes-from data_container –name test centos进入容器查看挂载情况docker exec -it test /bin/bash# df -h 查询挂载目录情况, 可以看到osxfs挂载了/var/mydata[root@f2b161477ab5 /]# df -hFilesystem Size Used Avail Use% Mounted onoverlay 59G 2.5G 54G 5% /tmpfs 64M 0 64M 0% /devtmpfs 1000M 0 1000M 0% /sys/fs/cgrouposxfs 234G 129G 103G 56% /var/mydata/dev/sda1 59G 2.5G 54G 5% /etc/hostsshm 64M 0 64M 0% /dev/shmtmpfs 1000M 0 1000M 0% /proc/acpitmpfs 1000M 0 1000M 0% /sys/firmware ...

January 6, 2019 · 1 min · jiezi

Docker的基本使用

拉取镜像:上篇文章 Docker入门安装教程 中讲了Docker最重要的一部分就是就是Docker镜像,我们所有的容器都是基于Docker镜像来创建的,这样才能保证所有同一个镜像创建出来的容器是一模一样的。而我们的容器则放在仓库中,Docker Hub就是一个官方公开的仓库,里面有非常多的镜像。我们可以直接使用官方仓库中的镜像来创建容器,下面我们就来创建一个nginx容器。使用docker search nginx命令来搜索镜像,可以看到搜索出来非常多的结果,注意,OFFICIAL那栏标记OK的就表示这个镜像是nginx官方制作的镜像。也可以直接到 Docker Hub 上去搜索镜像,可以更直观的看到镜像的信息。比如这里就提供了该镜像在Github上存放的对应不同的tag的Dockerfile文件,Dockerfile文件就是用来生成镜像用的,后续再讲怎么使用。一个tag就代表一个版本的镜像,因为nginx也有很多不同的版本,在拉取镜像的时候可以指定版本,如果没指定则默认拉取latest版本的镜像。下面使用docker pull命令拉取tag为1.15.8的nginx镜像。下载会需要一点时间,然后使用docker images命令来查看本地存在的镜像,发现已经有一个tag为1.15.8的nginx镜像了。运行容器:使用这个镜像运行起一个容器,运行docker run -d -p 9500:80 –name nginx nginx:1.15.8,对外映射的端口是9500,这里先不探究这些命令参数的含义,后续再细讲。使用docker ps命令来查看运行的容器,发现有一个名为nginx的容器,试着访问下,是可以成功打开nginx的主页。到现在为止我们已经成功的在官方仓库拉取一个镜像并通过镜像来运行起了一个容器。运行Docker容器:在拉取了Docker镜像之后我们可以使用docker run [镜像名:tag]命令来运行一个容器,这个命令后面可以添加参数来进行不同的操作,这里只简单介绍下上面用到的。-p 9500:80 这个参数的含义就是把容器的80端口映射到宿主机的9500端口,这样访问宿主机的9500端口就可以访问到nginx所在的容器了。–name nginx 为这个容器起一个名字。-d 后台运行。Docker容器和宿主机共享数据:因为容器是随时可以抛弃的,但是例如nginx和mysql这类需要存放数据的容器如果直接抛弃了旧的容器使用新的容器就会造成原来的数据丢失,所以就需要让这类容器的数据是和宿主机的某个目录下共享的,这样即使就容器删除了,新的容器只要指定数据的使用路径依旧可以使用旧容器的数据。只需要在运行容器时使用-v参数即可。使用这条命令运行容器时,会将容器这个目录下的数据共享到宿主机/var/lib/docker/volumes/目录下,会自动生成一个随机目录。docker run -d -p 9500:80 -v /etc/nginx/html –name nginx nginx:1.15.8可以使用docker inspect nginx或docker inspect –format={{.Mounts}} nginx命令来查看随机挂载到了宿主机哪个目录如果不想使用随机目录,也可以自己指定目录,使用冒号隔开即可,前面的是宿主机的目录,后面是容器的目录。docker run -d -p 9500:80 -v /usr/local/half/nginx/html:/etc/nginx/html –name nginx nginx:1.15.8启动容器时使用自己的配置文件:其实在镜像里面已经配置好了默认的配置文件路径,后续会讲到如何去查看这些配置路径。在nginx容器中配置文件的默认是/etc/nginx/nginx.conf,而在默认的配置文件里又讲页面的目录设置在了容器中的/etc/nginx/html中,所以其实可以使用上面的-v参数来使用自己的配置文件。使用-v参数替换默认的配置文件,下面的命令中第一个-v参数是使用宿主机/usr/local/half/nginx/ nginx.conf来替换了默认的配置文件,这是我提前创建好的一个nginx配置文件。在那里面我指定了将/etc/nginx/html目录作为页面的根目录,而第二个-v命令则是把这个目录挂载到宿主机下的/usr/local/half/nginx/html目录,那里面有一个自定义的index.html文件。所以这样一来当我们再访问这个容器时会发现主页是跳转到自定义的页面上。docker run -d -p 9500:80 -v /usr/local/half/nginx/nginx.conf:/etc/nginx/nginx.conf -v /usr/local/half/nginx/html:/etc/nginx/html –name nginx nginx:1.15.8而且因为我们把配置文件和存放页面的路径全都挂载在了宿主机的目录下,如果我们需要修改什么配置,只需要修改完之后再启动一个新的容器就可以了。进入到容器内部:因为镜像其实是根据Dockerfile构建出来的,官方的镜像也是如此,很多时候我们或许不清楚默认的配置路径,除了查文档和资料外还可以直接进入容器内部来查看。运行docker exec -i -t nginx /bin/bash命令就可以进入到容器内部,我们会发现其实即使一个简化版的Linux系统,所以我们可以使用一些基本的Linux命令来查询各种想要知道的信息。Dockerfile:Dockerfile文件是专门用来创建镜像的,最开始Docker Hub页面上也提供了Dockerfile文件在Github上的路径,例如nginx的Dockerfile。我个人对Dockerfile的理解就是把所用的下载和各种路径的配置都按顺序写好在Dockerfile文件中,然后再使用它构建出镜像。其实上面使用的-v等命令都是对应着Dockerfile中不同的指令,只不过是以命令行的方式调用了,覆盖了Dockerfile中的指令配置。这里就不作详细说明,如果有想要了解的可以阅读<<第一本Docker书>>里面很详细的讲了Dockerfile的使用。Docker常用命令:查看Docker信息,sudo docker info查看Docker镜像,sudo docker images查看Docker启动的容器,sudo docker ps查看Docker所有的容器,sudo docker ps -a删除Docker中某个容器,sudo docker rm [容器名字或id]删除Docker中所有的容器(查询出所有的容器id,传到rm命令中),sudo docker rm $(sudo docker ps -a -q)删除Docker中某个镜像,sudo docker rmi [镜像名字或id]强制删除Docker中某个镜像,sudo docker rmi -f [镜像名字或id]查找仓库中的镜像,sudo docker search [镜像名字]拉取仓库中的镜像,sudo docker pull [镜像名字]登录Docker Hub账号,sudo docker login推送镜像到Docker Hub仓库,sudo push [仓库/镜像名字:tag]总结:Docker在如今越来越流行,除了运维人员之外,后台开发也很有必要熟悉它,它可以极大的简化服务器的部署,本文只是粗略的介绍了基本使用,大家可以阅读<<第一本Docker书>>或者是官方文档等资料去了解更多更高级的功能。 ...

January 6, 2019 · 1 min · jiezi

体验URLOS自动快照备份 5分钟一次的快照备份真的很爽

近日,容器云管理面板URLOS更新了一个重大功能——快照备份,使用快照备份数据非常快速,只需1秒,而且恢复快照也不会中断当运行中的服务,快照体积也非常小(相比阿里云、腾讯的快照),使用起来相当舒爽!现在带大家体验一下,值得注意的是,要想使用快照功能,那么机器中必须配备两块硬盘,以阿里云主机为例,先加一个硬盘再说。阿里云最少要购买20G,购买后,首先要挂载硬盘选择要挂载到哪个实例上挂载完成后,通过SSH工具查看云主机硬盘信息,输入命令:fdisk -lOK,新硬盘添加成功!登录URLOS主控端 http://ip:9968 或 https://ip:8866,点击集群,选择【自动添加集群和节点】先填写云主机的SSH密码,再点击挂载硬盘这里要填写块存储设备,这个怎么填?我们之前通过fdisk -l命令查看了硬盘信息,把红框标注部分填进去如果你的主机内存不大于1G,请设置一下虚拟内存,如果内存为1G,则设置1G虚拟内存点击提交,等待节点部署完成。在URLOS应用市场中,几乎每一个应用都自带快照功能,下面以安装一个mysql服务和wordpress博客系统来做如何使用URLOS快照功能的说明。首先安装mysql5.7,这里不会详细讲解安装过程,详细过程可参考URLOS官方教程:https://www.urlos.com/center-…,现在直接看快照部分:目前该功能面向所有用户开放,但是免费版只能设置48小时的时间间隔,付费版则无限制。同样,在wordpress安装时也可以选择快照备份时间间隔如何管理快照,在已安装的服务后面,点击“更多”按钮,显示出快照列表和手动快照备份选项先看快照列表标红框的就是快照存放的目录,可以通过sftp工具去查看,如需要恢复快照,点击【恢复快照】即可,恢复时,程序会先对当前状态进行1次备份,然后再恢复成你指定的快照。URLOS除了支持自动快照外,还支持手动快照,手动快照时填写快照描述URLOS快照功能十分强大,尤其是自动快照,有了这个功能的保驾护航,相信你的网站数据安全又多了一份保障。值得注意的是,现阶段,网站服务和数据库是需要分别备份的,比如wordpress网站快照只是备份了网站文件内容,数据库方面另外在mysql服务中备份。而且数据库服务的快照是将当前服务里的所有数据库整体快照,恢复时也是整体恢复,所以,URLOS官方建议创建1个网站服务则对应创建1个数据库服务,这样能保证该网站数据库恢复时不影响其他网站的数据库。

January 5, 2019 · 1 min · jiezi

docker 学习笔记

概念image 镜像 对应于一个操作系统。一个 ubuntu 18.10 的镜像就是 ubuntu1810:latest。 docker images 可用查看本机可以使用的镜像 docker pull xxxx/ubuntu1810:latest 就从远程下载一个镜像 container 实例由某个镜像实例化运行的一个容器。docker run ubuntu1810:latest 就会运行 ubuntu1810:latest 镜像的某个实例,理论上可用这样运行无数个 ubunu 实例。 - repo 仓库 镜像统一存放的地方。官方有一个镜像仓库,也可用创建自己私有的镜像仓库。当使用 docker run xxx 而本地没有 xxx 的 image 的时候,会自动去远程 pull。 命令行docker ps 查看当前正在运行的实例docker images 查看本地的所有镜像docker run xxxx 实例化一个镜像本运行他的实例docker stop 停止某个实例docker build . -t xxxx 用当前目录的 Dockerfile 编译一个镜像打上标签 xxxx

January 4, 2019 · 1 min · jiezi

Docker 快速验证:不转发让 Tomcat 绑定 80 端口

前言之前写过 tomcat 单机多实例,最后解决 80 端口访问用的是 iptables 转发;第三轮投产前,客户做了迁移和扩容。重启后,不但转发策略失效,重新执行转发命令后仍旧不能访问 80。后来我还发现是彻底关闭了防火墙。后来经研究,通过打开防火墙,配置自定义防火墙函数保存iptables策略,解决了 iptables 转发重启失效的问题;方案也提交了客户。方案稍后整理发布。第三轮投产时,通过和一线沟通,得知客户这边的策略是生产一律关闭防火墙。so strange!但是,还得想办法不是?总不能说这个系统必须得开防火墙吧?通过研究实验和请教大神,最终搞定。整理如下。# 进入docker容器启动tomcatroot@40f7130d7832:/usr/local/tomcat/bin# ./startup.sh # get tomcat对应java进程:root@40f7130d7832:/usr/local/tomcat/bin# ps -ef | grep java# 添加kch用户root@40f7130d7832:/usr/local# # groupadd kch && useradd -d /kch -g kch -m kch && passwd kch# 更改tomcat属主为kchroot@40f7130d7832:/usr/local# chown -R kch.kch tomcat# 赋予 tomcat对应java进程 u+s 权限 (进程只能是exe,不能是脚本):普通用户访问时,临时使进程具有root权限可以绑定80端口root@40f7130d7832:/usr/local# chmod u+s /docker-java-home/jre/bin/java实验如下本能就用官方 tomcat7 镜像。没有的自行 pull 一个docker pull tomcat:7.0默认是 8080 端口的,启动命令docker run –name w1 -it -p 8080:8080 tomcat:7.0 /bin/bash这里我们需要绑定 80 端口,所以启动命令见下文,且需要修订 server.xml,把 tomcat 的端口由 8080 改为 80;启动 Tomcat 镜像ChinaDreams:workspace kangcunhua$ docker imagesREPOSITORY TAG IMAGE ID CREATED SIZE….tomcat 7.0 3402a4bb8ae6 4 months ago 357MB….ChinaDreams:workspace kangcunhua$ docker run –name www -it -p 80:80 tomcat:7.0 /bin/bashroot@40f7130d7832:/usr/local/tomcat# cd binroot@40f7130d7832:/usr/local/tomcat/bin# ./startup.shroot@40f7130d7832:/usr/local/tomcat/bin# ps -ef | grep javaroot 12 1 37 15:12 pts/0 00:00:05 /docker-java-home/jre/bin/java -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Djava.endorsed.dirs=/usr/local/tomcat/endorsed -classpath /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar -Dcatalina.base=/usr/local/tomcat -Dcatalina.home=/usr/local/tomcat -Djava.io.tmpdir=/usr/local/tomcat/temp org.apache.catalina.startup.Bootstrap startroot 56 1 0 15:12 pts/0 00:00:00 grep javaroot@40f7130d7832:/usr/local/tomcat/bin# curl localhost:8080<!DOCTYPE html>…root@40f7130d7832:/usr/local/tomcat/bin#get 到 tomcat 的进程(exe):/docker-java-home/jre/bin/java。注:这里查询记下来的,必须是进程(exe),不能是脚本。后续有用;修改 server.xmlChinaDreams:workspace kangcunhua$ docker cp www:/usr/local/tomcat/conf/server.xml .ChinaDreams:workspace kangcunhua$ vi server.xml ChinaDreams:workspace kangcunhua$ docker cp server.xml www:/usr/local/tomcat/conf/将默认的 8080 改成 80找到<Connector port=“8080"修改为<Connector port=“80"新建 prms 用户对应容器已新建 prms 用户root@40f7130d7832:/usr/local# # groupadd kch && useradd -d /kch -g kch -m kchroot@40f7130d7832:/usr/local# # passwd kch更改 tomcat 属主root@40f7130d7832:/usr/local# ls -ladrwxr-sr-x 14 root staff 4096 Dec 18 15:12 tomcatroot@40f7130d7832:/usr/local# chown -R kch.kch tomcatroot@40f7130d7832:/usr/local# ls -ladrwxr-sr-x 20 kch kch 4096 Dec 18 15:22 tomcatroot@40f7130d7832:/usr/local#启动发现可以正常启动,但是不能访问80端口;$ ./startup.sh…Tomcat started.$ curl localhostcurl: (7) Failed to connect to localhost port 80: Connection refused$ ./shutdown.sh修订 java 的属主root@40f7130d7832:/usr/local# ls -la /docker-java-home/jre/bin/java-rwxr-xr-x 1 root root 6408 May 19 2017 /docker-java-home/jre/bin/javaroot@40f7130d7832:/usr/local# chmod u+s /docker-java-home/jre/bin/javaroot@40f7130d7832:/usr/local# ls -la /docker-java-home/jre/bin/java-rwsr-xr-x 1 root root 6408 May 19 2017 /docker-java-home/jre/bin/java启动 tomcat正常启动,且可以访问 80 端口通过浏览器http://localhost也可以访问,看到tomcat首页;root@40f7130d7832:/usr/local# su kch$ ./startup.shUsing CATALINA_BASE: /usr/local/tomcatUsing CATALINA_HOME: /usr/local/tomcatUsing CATALINA_TMPDIR: /usr/local/tomcat/tempUsing JRE_HOME: /docker-java-home/jreUsing CLASSPATH: /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jarTomcat started.$ curl localhost<!DOCTYPE html>…$收尾提交镜像docker commit www tomcat-bind80:7.0提交 dockerhubChinaDreams:workspace kangcunhua$ docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEtomcat-bind80 7.0 c6e1013adaf9 6 seconds ago 374MBChinaDreams:workspace kangcunhua$ docker tag c6e1013adaf9 aninputforce/tomcat7-bind80:latestChinaDreams:workspace kangcunhua$ docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEtomcat-bind80 7.0 c6e1013adaf9 2 minutes ago 374MBaninputforce/tomcat7-bind80 latest c6e1013adaf9 2 minutes ago 374MBChinaDreams:workspace kangcunhua$ docker push aninputforce/tomcat7-bind80使用镜像ChinaDreams:workspace kangcunhua$ docker pull aninputforce/tomcat7-bind80ChinaDreams:workspace kangcunhua$ docker run –name www -it -p 80:80 aninputforce/tomcat7-bind80 /bin/bashroot@ff63d8ac4776:/usr/local/tomcat# su kch$ pwd/usr/local/tomcat$ cd bin$ ./startup.shUsing CATALINA_BASE: /usr/local/tomcatUsing CATALINA_HOME: /usr/local/tomcatUsing CATALINA_TMPDIR: /usr/local/tomcat/tempUsing JRE_HOME: /docker-java-home/jreUsing CLASSPATH: /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jarTomcat started.$ curl localhost<!DOCTYPE html>….参考链接Is there a way for non-root processes to bind to “privileged” ports on Linux?请添加链接描述Bye这几篇笔记写完。对linux的认识更加深刻了。不过对于80端口的绑定,被真实商业环境折磨许久,研究分析实现了种种可能。后续或许会追加笔记为“茴香豆系列” :)51Reboot K8s专场分享时间:2019年1月4号21:00-22:00分享讲师:GY 老师10年一线软件开发经验,先后经历了传统安全公司,以及多家互联网公司;在安全开发方面,曾开发过 Linux 防火墙、web 应用防火墙、Linux 安全内核加固,基于大流量的 Web 安全威胁分析等项目;在互联网公司工作时,曾基于 DPDK 高性能网络开发框架开发过基于全流量的网络流量分析平台和基于 Sflow 网络流量分析平台,基于 Golang 开发 SmartDNS 等;开发语言也是从C -> python -> golang 的转变过程?现从事基于 K8S 和 Docker在私有云平台建设方面的研发工作;具备丰富的Linux系统开发经验、网络开发经验以及项目管理经验;目前开发工作 90+% 都在用 Golang,Golang 是一门简洁、高效、强大且灵活的编程语言。参与方式:添加小助手wechat:18310139238,备注:公开课,拉入直播分享群与老师互动 ...

January 4, 2019 · 2 min · jiezi

基于kubernetes+docker+jenkins的DevOps实践

基于kubernetes+docker+jenkins的DevOps实践之前自己的项目开发就搭了个cicd的环境,那时候是在本就小的可怜的服务器上搭了一套 jenkins + docker registry + docker 见之前的笔记 docker学习下面 总的差不多这样: 之后对kubernetes的接触后,就在之前的基础上加入kubernetes,其实也就是在服务器拉取镜像docker run的时候改变为通知kubernetes的apiServer对提前配置好的项目配置文件xx.yaml进行更新kubectl appply -f xx.yaml,它会对配置里的镜像拉取在多个pod里运行,当然还需要对应的service,如果需要暴露给外部还可以添个ingress。 一个小服务器加本地一个闲置从机撑进去这么多东西很显然爆了,于是把jenkins , docker registry拆出来,用上了公共的ali云服务CodePipeline,容器镜像服务。 这里记录一下。docker搭建ubuntu安装docker官方教程kubernetes搭建之前写的kubernetes学习下面有使用ali云CodePipeline替代jenkins创建任务配置->项目名称:最好为github上代码的demo项目名称,这里以bootshiro为例配置->源码管理->Git:URL为github上的项目clone url,下面默认master分支配置->构建触发器->填写代码分支:eg:master 点击生成触发器地址留下备用(github webhook配置会用到)配置->构建项目类型可选maven项目 node python等(按自己需求改编译打包册测试脚本)eg: maven项目 编译打包: mvn package -B -DskipTests 用例测试: mvn test配置->镜像构建和发布: 这里使用ali云的免费docker镜像仓库镜像版本号最好用jenkins环境变量标记,registry地址证书等就是自己开通的ali云registry地址和账户,docker路径是相对于当前代码仓库的Dcokerfile文件路径,用这个Dockefile文件来生成镜像。eg: bootshiro的Dockefile#VERSION 1.1.0#基础镜像为openjdk:12-alpineFROM openjdk:12-alpine#签名MAINTAINER tomsun28 “tomsun28@outlook.com"RUN rm -rf /opt/running/bootshiro*ADD ./target/bootshiro.jar /opt/running/bootshiro.jarEXPOSE 8080WORKDIR /opt/running/CMD [“java”, “-jar”, “bootshiro.jar”,”–spring.profiles.active=prod"]配置->部署Kubernetes(新): 这里配置对搭建好的k8s环境的apiServer连接,之后好使用apiServer对kubernetes操作认证方式:选择认证证书API服务器地址:为apiServer通讯地址证书:使用docker授权模式,客户端Key(key.pem)和客户端证书(cert.pem)在/etc/kubernetes/admin.conf,服务端CA证书(ca.pem)在/etc/kubernetes/pki/ca.crt部署配置文件:为k8s部署这个项目的配置文件位置,也是以当前项目代码仓库为相对路径,eg :bootshiro.yaml# ———————-bootshiro——————— ## ——bootshiro deployment—— #kind: DeploymentapiVersion: apps/v1beta2metadata: name: bootshiro-deployment labels: app: bootshirospec: replicas: 1 selector: matchLabels: app: bootshiro template: metadata: labels: app: bootshiro spec: containers: - name: nginx image: registry.cn-hangzhou.aliyuncs.com/tomsun28/bootshiro:${BUILD_NUMBER} ports: - containerPort: 8080—# ——-nginx-service——— #apiVersion: v1kind: Servicemetadata: name: bootshiro-servicespec:# type: NodePort ports: - name: server port: 8080 targetPort: 8080 selector: app: bootshiro# !———————-bootshiro——————— #这里配置部署文件创建了一个pod实例,创建了与其想对应的service在集群内部暴露服务。如果部署的应用需要对集群外提供服务,这里还要创建对应的暴露服务的方式,如ingress, nodeport等-到此cicd就差不多了,我们开发代码push到github仓库上,跟着DevOps流程走,最后项目就会自己运行到kubernetes集群里面了,pod挂了或者从机挂了,k8s会重新启保证设定数量的pod。使用ingress对集群外暴露服务这里使用的是traefik-ingress,在kubernetes中部署traefik有官方部署手册,基本按着走一遍就能部署上去了。 整合部署的traefik.yaml:—kind: ClusterRoleapiVersion: rbac.authorization.k8s.io/v1beta1metadata: name: traefik-ingress-controllerrules: - apiGroups: - "" resources: - services - endpoints - secrets verbs: - get - list - watch - apiGroups: - extensions resources: - ingresses verbs: - get - list - watch—kind: ClusterRoleBindingapiVersion: rbac.authorization.k8s.io/v1beta1metadata: name: traefik-ingress-controllerroleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: traefik-ingress-controllersubjects:- kind: ServiceAccount name: traefik-ingress-controller namespace: kube-system—apiVersion: v1kind: ServiceAccountmetadata: name: traefik-ingress-controller namespace: kube-system—kind: DaemonSetapiVersion: extensions/v1beta1metadata: name: traefik-ingress-controller namespace: kube-system labels: k8s-app: traefik-ingress-lbspec: template: metadata: labels: k8s-app: traefik-ingress-lb name: traefik-ingress-lb spec: serviceAccountName: traefik-ingress-controller terminationGracePeriodSeconds: 60 containers: - image: traefik name: traefik-ingress-lb ports: - name: http containerPort: 80 hostPort: 80 - name: admin containerPort: 8080 securityContext: capabilities: drop: - ALL add: - NET_BIND_SERVICE args: - –api - –kubernetes - –logLevel=INFO—apiVersion: v1kind: Servicemetadata: name: traefik-web-ui namespace: kube-systemspec: selector: k8s-app: traefik-ingress-lb ports: - name: web port: 80 targetPort: 8080—apiVersion: extensions/v1beta1kind: Ingressmetadata: name: traefik-web-ui namespace: kube-system annotations: kubernetes.io/ingress.class: traefik traefik.frontend.rule.type: PathPrefixStripspec: rules: - host: tom.usthe.com http: paths: - path: /ingress backend: serviceName: traefik-web-ui servicePort: web使用traefik来暴露service:apiVersion: extensions/v1beta1kind: Ingressmetadata: name: chess namespace: default annotations: kubernetes.io/ingress.class: traefik traefik.frontend.rule.type: PathPrefixStripspec: rules: - host: tom.usthe.com http: paths: - path: /usthe backend: serviceName: usthe-service servicePort: http - path: /nginx backend: serviceName: nginx servicePort: http转载请注明 from tomsun28 ...

January 4, 2019 · 2 min · jiezi

nginx docker容器配置https(ssl)

证书生成首先需要有https的证书文件,如果你已经向证书授权中心购买了证书,可以跳过这步,这里介绍如何生成自签名证书,自签名证书是指不是证书授权中心(Certificate Authority)颁发的证书,而是在个人计算机上通过相关工具自己生成的证书,一般用于测试,不可用于生产环境。为了方便管理证书(证书生成过程中会产生很多文件),我们可以单独创建一个目录用于存放证书文件,下面是通过openssl工具生成证书的过程。1. 创建目录$ cd ~$ mkdir ssl$ cd ssl2. 创建秘钥文件创建秘钥文件definesys.key,名称可以自定义,需要指定密码(随意密码即可)$ openssl genrsa -des3 -out definesys.key 1024Generating RSA private key, 1024 bit long modulus…….++++++………………++++++e is 65537 (0x10001)Enter pass phrase for definesys.key:Verifying - Enter pass phrase for definesys.key:3. 创建csr证书需要输入相关信息,比较重要的是Common Name,这个是访问nginx的地址$ openssl req -new -key definesys.key -out definesys.csrEnter pass phrase for definesys.key:You are about to be asked to enter information that will be incorporatedinto your certificate request.What you are about to enter is what is called a Distinguished Name or a DN.There are quite a few fields but you can leave some blankFor some fields there will be a default value,If you enter ‘.’, the field will be left blank.—–Country Name (2 letter code) [AU]:CNState or Province Name (full name) [Some-State]:ShanghaiLocality Name (eg, city) []:ShanghaiOrganization Name (eg, company) [Internet Widgits Pty Ltd]:DefinesysOrganizational Unit Name (eg, section) []:DefinesysCommon Name (e.g. server FQDN or YOUR name) []:www.definesys.comEmail Address []:jianfeng.zheng@definesys.comPlease enter the following ’extra’ attributesto be sent with your certificate requestA challenge password []:可以不用输An optional company name []:可以不用输#此时文件$ ssl lltotal 16-rw-r–r– 1 asan staff 733 1 3 23:57 definesys.csr-rw-r–r– 1 asan staff 963 1 3 23:55 definesys.key4. 去除秘钥密码nginx使用私钥时需要去除密码,执行以下命令时需要输入秘钥的密码$ cp definesys.key definesys.key.bak$ openssl rsa -in definesys.key.bak -out definesys.keyEnter pass phrase for definesys.key.bak:writing RSA key5. 生成crt证书$ openssl x509 -req -days 3650 -in definesys.csr -signkey definesys.key -out definesys.crtSignature oksubject=/C=CN/ST=Shanghai/L=Shanghai/O=Definesys/OU=Definesys/CN=www.definesys.com/emailAddress=jianfeng.zheng@definesys.comGetting Private key#此时文件列表$ ssl lltotal 32-rw-r–r– 1 asan staff 1017 1 4 00:03 definesys.crt-rw-r–r– 1 asan staff 733 1 3 23:57 definesys.csr-rw-r–r– 1 asan staff 887 1 4 00:02 definesys.key-rw-r–r– 1 asan staff 963 1 4 00:01 definesys.key.baknginx容器配置1. 证书文件上传将definesys.crt文件和definesys.key文件拷贝到服务器上,假设你服务器上nginx的配置文件在/etc/nginx/目录下,可以在该目录下创建一个文件夹,这里命名certs,将文件拷贝至该文件夹下。2. 配置文件修改修改配置文件nginx.confserver { listen 443 ssl; server_name www.definesys.com; ssl_certificate /etc/nginx/certs/definesys.crt; ssl_certificate_key /etc/nginx/certs/definesys.key; ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; location / { root /usr/share/nginx/html; index index.html index.htm; }}如果server配置不在nginx.conf文件上,可以在conf.d文件夹下找.conf后缀的文件,一般有个default.conf文件。3. 启动容器docker run -d –restart=unless-stopped -p 443:443 -v /etc/nginx/:/etc/nginx -v /var/run/docker.sock:/tmp/docker.sock:ro -v /u01/application:/usr/share/nginx/html nginx访问https://localhost验证配置是否正确,如果能够正常访问说明配置成功,由于是自签名证书,打开时会提示证书不安全,忽略即可。 ...

January 4, 2019 · 2 min · jiezi

深度学习批任务处理调度器与kubernetes默认调度器融合

kubernetes集群三步安装什么是批处理任务深度学习中经常会出现多机多卡的任务,也就是同事会起多个pod,但是这多个pod属于同一个任务。这样就会有一个问题一个任务要起100个pod,每个pod需要一张卡,总共需要100张GPU卡,而集群中只有99张空闲的GPU卡,这样默认的k8s调度器会如何处理?因为默认调度器是一个一个pod调度的,只会检查单个pod资源够不够,这样前99个都能成功,最后一个pod调度失败。 这样非常有可能造成任务跑不了前99个占着GPU不释放,新的任务无法调度严重时整个集群死锁,都“占着茅坑不拉屎”所以需要在调度时对整个task所需所有资源进行检查,当集群总体资源不够时,一个pod都得不到调度。社区提供了一个能支持这种特性的调度器但是这个调度器是没办法和原生调度器很好的配合工作的最大的问题在于两个调度器都有cache,这样cache的内容会冲突,导致调度混乱这个调度器没法和原生调度器同时起作用,这样用了这个batch调度器后就没法用亲和性什么的特性了所以我们做的事是将两者特性融合,选择的方法是定制化开发kube-scheduler其实scheduler是可以通过extender扩展的,但是extender还是太弱了,它仅能在预选和优选过程中加入自己的过滤策略,而这对于批处理任务远远不够。实现难点需要优选时加batch任务检查拿到一个pod —> 如果是一个batchpod —> 查询集群资源是否满足batch任务—>否调度失败需要保障batch任务中其它pod能得到调度如果集群资源能满足这个batch任务直接去bind有个问题:假设调度队列是这样,假设集群中有三个GPU,而batch任务需要三个GPU:A batch pod ->pod ->pod ->A batch pod ->A batch pod集群资源够 调度成功调度了别的pod调度了别的podGPU被别的pod占用 GPU不够 失败GPU不够 失败所以最终结果是A批任务占用了一个GPU但是整个任务是调度失败的,那一个GPU还得不到释放所以需要修改pod调度队列里的顺序?让A batch pod连续调度? 没这么简单,pod调度是创建协程并发调度的,这样即便去调整任务队列里pod的顺序也不一定能保证batch任务其它pod能得到优先调度。go wait.Until(sched.scheduleOne, 0, sched.config.StopEverything)只要batch pod走到Bind逻辑了就没有回头路了batch任务中所有pod先进行assume调度,其中任意一个失败就清理掉其它已经bind但是还没实际进行调度的pod。 并把所有pod扔回队列,或者直接返回调度失败清理改任务的pod,让上层重新触发?scheduler流程 scheduler/sheduler.go scheduleOne逻辑:选节点->cache assume pod on node-> 创建协程bind所以在assume时去检查,不满足退还已经调度的pod是不可行的,因为之前batch任务中的pod可能已经bind过了, 所以只能batch任务中最后一个pod得到确认才能去bind前面的pod预占用策略预占用策略: 第一个batch pod任务来时,检查集群资源是不是够,如果够进行预占,把其它几个node打上标记,让接下来pod无法占用其它的node,这样batch任务其实pod过来就有节点可用。回到了不能bind的问题。。。这种问题有两点:如何知道batch任务中其它pod需要什么样的节点,如果pod都一样问题可简化如果后面的pod失败了,第一个pod还是已经bind,还是会出现一样的问题最终还是在所有pod assume之前不能bind单个pod综上,需要在几个地方处理队列最好用优先级队列,把正在调度的pod的关联pod优先级提高选节点时做判断,看集群资源是否够选好节点assume pod时检查,如果自己不够或者pod组不够就不去bind问题是之前的pod已经走了bind流程,所以最重要的是如何解决让之前的pod不去bind,延迟bind最终方案 - 延迟绑定方案:在batch任务bind时进行特殊处理如果是batch任务扔进task cache,不进行binding如果batch任务最后一个pod扔进task cache,该task ready,放进bind队列在bind队列里取task 进行bind,task互斥锁与普通pod bind时互斥使用batch任务使用,pod增加两个注解: annotations: scheduling.k8s.io/group-name: qj-1 scheduling.k8s.io/group-pod-num: 3pod加上这两个注解表示属于同一个task, num表示task里有多少pod。本来是再定义一个CRD去描述这个task,耦合会小一些,但是实现麻烦些,需要多监听一个CRD,偷懒就没这样做实现延迟绑定流程:如果是普通的pod,找到节点后assume就直接bind如果是批处理任务,直接扔到批处理缓存中返回有个协程一直检查批缓存中是否有成功的task (pod都齐了)成功的task扔进binding队列,worker取成功的task进行批量绑定,绑定时与普通pod互斥batch scheduler接口与成员Run 起一个协程检查成功的task并塞入队列RunBind 起一个task绑定协程PodQuePriority 去动态修改pod队列的优先级,让同task的pod优先调度执行流程:延迟绑定scheduler/scheduler.go: //fanux if it is a batch pod, return if sched.Config.BatchScheduler.IsBatchPod(assumedPod) { err = sched.Config.BatchScheduler.HandleBatchPod(assumedPod) if err != nil { glog.Errorf(“schedule batch pod failed: %v”, assumedPod.Namespace, assumedPod.Name) } return }增加绑定互斥,防止batch任务和普通pod同事binding: go func() { //fanux add bind mutex sched.Config.BatchScheduler.Lock() defer sched.Config.BatchScheduler.UnLock() err := sched.bind(assumedPod, &v1.Binding{检查资源是否充足CheckResourceIsEnoughshould’t use filterFunc, needs nodelistscheduler/util/batch.gopackage utilimport “api/core/v1”//CheckResourceIsEnough isfunc CheckResourceIsEnough(pod *v1.Pod, nodes []*v1.Node) (bool, error) { return false, nil}scheduler/core/generic_scheduler.go //fanux add checkBatchPodResource flag, err := util.CheckResourceIsEnough(pod, filteredNodes) if !flag || err != nil { return “”, err } trace.Step(“Prioritizing”)处理资源不足时的情况 suggestedHost, err := sched.schedule(pod) //fanux add handle if resource not enough if strings.Contains(err.Error(), common.BatchResourceNotEnough) { sched.Config.BatchScheduler.HandleResourceNotEnough(pod) } else if err != nil {如何获取节点已经分配GPU的数量nodeInfo allocatableResource - requestedResource is avaliavle resource requestedResource *Resource nonzeroRequest *Resource allocatableResource *ResourceGPU 是 ScalarResources, 资源名称叫 : NVIDIAGPUResourceName = “nvidia.com/gpu"type Resource struct { MilliCPU int64 Memory int64 EphemeralStorage int64 // We store allowedPodNumber (which is Node.Status.Allocatable.Pods().Value()) // explicitly as int, to avoid conversions and improve performance. AllowedPodNumber int // ScalarResources ScalarResources map[v1.ResourceName]int64}增加podupdater,可更新podcondition状态 batchScheduler := batch.NewBatchScheduler(c.schedulerCache, c.podQueue, &binder{c.client}, &podConditionUpdater{c.client})需要把batch scheduler的cache给generic_scheduler资源检查时需要用需要知道已经有哪些pod已经assume过了,把这个数量减掉才是batch任务还需要多少GPUcore/generic_scheduler.go //fanux add batch Cache //check batch pod resource is enough need batch scheduler cache BatchCache common.TaskCache //fanux add checkBatchPodResource flag, err := common.CheckResourceIsEnough(pod, filteredNodes, g.cachedNodeInfoMap, g.BatchCache)factory.go //fanux check batch resource is enough need batch scheduler cache batchCache := batchScheduler.GetTaskCache() algo := core.NewGenericScheduler( … batchCache, )then checkresource : //shoud not use metadata, need use metadata - assumed pod num in batch cache _, podNum := GetPodBathMeta(pod) podNum -= batchCache.GetTaskAssumedPodNum(pod)检查资源是否充足详细算法:有很多细节//获取pod需要多少GPU,这个需要把pod里容器配额加起来func GetPodGPUCount(pod *v1.Pod) (count int) { for _, c := range pod.Spec.Containers { limit, ok := c.Resources.Limits[NVIDIAGPUResourceName] l, okay := limit.AsInt64() if !ok || !okay { continue } count += int(l) } glog.Infof(“Pod [%s] need GPU [%d]”, pod.GetName(), count) return}//获取节点空闲GPU,需要把可分配的减去已经申请的func GetNodeFreeGPU(nodeInfo *cache.NodeInfo) int { if nodeInfo == nil { return 0 } allocatable, ok := nodeInfo.AllocatableResource().ScalarResources[NVIDIAGPUResourceName] if !ok { glog.Errorf(“can’t fetch allocatable GPU : %v”, nodeInfo) return 0 } glog.Infof(“node [%s] allocatable GPU [%d]”, nodeInfo.Node().Name, allocatable) requested, ok := nodeInfo.RequestedResource().ScalarResources[NVIDIAGPUResourceName] if !ok { //glog.Errorf(“can’t fetch requested GPU : %v”, nodeInfo) //return 0 requested = 0 } glog.Infof(“node [%s] requested GPU [%d]”, nodeInfo.Node().Name, requested) available := allocatable - requested glog.Infof(“available node [%s] GPU : [%d]”, nodeInfo.Node().Name, available) return int(available)}//这里最关键的点是需要把annotations里面获取的task pod总数减去已经assume过的batch pod,这样才是真实所需func CheckResourceIsEnough(pod *v1.Pod, nodes []*v1.Node, cachedNodeInfoMap map[string]*cache.NodeInfo, batchCache TaskCache) (bool, error) { //if is not batch pod, return true,nil if !IsBatch(pod) { glog.Infof(“pod %s is not batch pod”, pod.GetName()) return true, nil } //shoud not use metadata, need use metadata - ready pod num in batch cache _, podNum := GetPodBathMeta(pod) podNum -= batchCache.GetTaskAssumedPodNum(pod) everyPodNeedsGPU := GetPodGPUCount(pod) if everyPodNeedsGPU == 0 { glog.Infof(“pod %s require 0 GPU”, pod.GetName()) return true, nil } // TODO maybe check nodes[1:], node[0] already allocate a pod, CPU and other metric may reach limit for _, node := range nodes { nodeInfo, ok := cachedNodeInfoMap[node.Name] if !ok { continue } nodeFree := GetNodeFreeGPU(nodeInfo) podNum -= nodeFree / everyPodNeedsGPU glog.Infof(“pod: [%s] node: [%s] podNum [%d] nodeFree [%d] podNeed [%d]”, pod.GetName(), node.Name, podNum, nodeFree, everyPodNeedsGPU) if podNum <= 0 { return true, nil } } return false, fmt.Errorf(“BatchResourceNotEnough : pod name is %s”, pod.GetName())}//判断是不是batch podfunc IsBatch(pod *v1.Pod) bool { g, n := GetPodBathMeta(pod) if g == "” || n == 0 { glog.Infof(“The pod’s group name is empty string,pod name is %v.”, pod.GetName()) return false } return true}关于GPU的使用与发现资源包这里包含docker nv-docker GPU-device plugininstall.sh…/etc/docker/daemon.json[root@compute-gpu006 ~]# cat /etc/docker/daemon.json{ “default-runtime”:“nvidia”, “runtimes”: { “nvidia”: { “path”: “/usr/bin/nvidia-container-runtime”, “runtimeArgs”: [] } }}kubectl describe node xxx:Capacity: cpu: 72 ephemeral-storage: 222779Mi hugepages-1Gi: 0 hugepages-2Mi: 2Gi memory: 791014684Ki nvidia.com/gpu: 2 # 这里就能看到GPU了 pods: 110Allocatable: cpu: 72 ephemeral-storage: 210240641086 hugepages-1Gi: 0 hugepages-2Mi: 2Gi memory: 788815132Ki nvidia.com/gpu: 2 pods: 110总结原生调度器的设计就是pod one by one,所以做这个功能的开发还是改动非常大的,也是比较困难的,工作量不大,但是需要找到一个优雅的方案,合理的架构比较麻烦,想了很久做了这个侵入不太大的实现方案,欢迎大家一起讨论公众号: ...

January 3, 2019 · 3 min · jiezi

使用prometheus operator监控envoy

kubernetes集群三步安装概述prometheus operator应当是使用监控系统的最佳实践了,首先它一键构建整个监控系统,通过一些无侵入的手段去配置如监控数据源等故障自动恢复,高可用的告警等。。不过对于新手使用上还是有一丢丢小门槛,本文就结合如何给envoy做监控这个例子来分享使用prometheus operator的正确姿势至于如何写告警规则,如何配置prometheus查询语句不是本文探讨的重点,会在后续文章中给大家分享,本文着重探讨如何使用prometheus operatorprometheus operator安装sealyun离线安装包内已经包含prometheus operator,安装完直接使用即可配置监控数据源原理:通过operator的CRD发现监控数据源service启动envoyapiVersion: apps/v1kind: Deploymentmetadata: name: envoy labels: app: envoyspec: replicas: 1 selector: matchLabels: app: envoy template: metadata: labels: app: envoy spec: volumes: - hostPath: # 为了配置方便把envory配置文件挂载出来了 path: /root/envoy type: DirectoryOrCreate name: envoy containers: - name: envoy volumeMounts: - mountPath: /etc/envoy name: envoy readOnly: true image: envoyproxy/envoy:latest ports: - containerPort: 10000 # 数据端口 - containerPort: 9901 # 管理端口,metric是通过此端口暴露—kind: ServiceapiVersion: v1metadata: name: envoy labels: app: envoy # 给service贴上标签,operator会去找这个servicespec: selector: app: envoy ports: - protocol: TCP port: 80 targetPort: 10000 name: user - protocol: TCP # service暴露metric的端口 port: 81 targetPort: 9901 name: metrics # 名字很重要,ServiceMonitor 会找端口名envoy配置文件:监听的地址一定需要修改成0.0.0.0,否则通过service获取不到metric/root/envoy/envoy.yamladmin: access_log_path: /tmp/admin_access.log address: socket_address: protocol: TCP address: 0.0.0.0 # 这里一定要改成0.0.0.0,而不能是127.0.0.1 port_value: 9901static_resources: listeners: - name: listener_0 address: socket_address: protocol: TCP address: 0.0.0.0 port_value: 10000 filter_chains: - filters: - name: envoy.http_connection_manager config: stat_prefix: ingress_http route_config: name: local_route virtual_hosts: - name: local_service domains: ["*"] routes: - match: prefix: “/” route: host_rewrite: sealyun.com cluster: service_google http_filters: - name: envoy.router clusters: - name: service_sealyun connect_timeout: 0.25s type: LOGICAL_DNS # Comment out the following line to test on v6 networks dns_lookup_family: V4_ONLY lb_policy: ROUND_ROBIN hosts: - socket_address: address: sealyun.com port_value: 443 tls_context: { sni: sealyun.com }使用ServiceMonitorenvoyServiceMonitor.yaml:apiVersion: monitoring.coreos.com/v1kind: ServiceMonitormetadata: labels: app: envoy name: envoy namespace: monitoring # 这个可以与service不在一个namespace中spec: endpoints: - interval: 15s port: metrics # envoy service的端口名 path: /stats/prometheus # 数据源path namespaceSelector: matchNames: # envoy service所在namespace - default selector: matchLabels: app: envoy # 选择envoy servicecreate成功后我们就可以看到envoy的数据源了:然后就可以看到metric了:然后就可以在grafana上进行一些配置了,promethues相关使用不是本文讨论的对象告警配置alert manager配置[root@dev-86-201 envoy]# kubectl get secret -n monitoringNAME TYPE DATA AGEalertmanager-main Opaque 1 27d我们可以看到这个secrect,看下里面具体内容:[root@dev-86-201 envoy]# kubectl get secret alertmanager-main -o yaml -n monitoringapiVersion: v1data: alertmanager.yaml: Imdsb2JhbCI6IAogICJyZXNvbHZlX3RpbWVvdXQiOiAiNW0iCiJyZWNlaXZlcnMiOiAKLSAibmFtZSI6ICJudWxsIgoicm91dGUiOiAKICAiZ3JvdXBfYnkiOiAKICAtICJqb2IiCiAgImdyb3VwX2ludGVydmFsIjogIjVtIgogICJncm91cF93YWl0IjogIjMwcyIKICAicmVjZWl2ZXIiOiAibnVsbCIKICAicmVwZWF0X2ludGVydmFsIjogIjEyaCIKICAicm91dGVzIjogCiAgLSAibWF0Y2giOiAKICAgICAgImFsZXJ0bmFtZSI6ICJEZWFkTWFuc1N3aXRjaCIKICAgICJyZWNlaXZlciI6ICJudWxsIg==kind: Secretbase64解码一下:“global”: “resolve_timeout”: “5m"“receivers”:- “name”: “null"“route”: “group_by”: - “job” “group_interval”: “5m” “group_wait”: “30s” “receiver”: “null” “repeat_interval”: “12h” “routes”: - “match”: “alertname”: “DeadMansSwitch” “receiver”: “null"所以配置alertmanager就非常简单了,就是创建一个secrect即可如alertmanager.yaml:global: smtp_smarthost: ‘smtp.qq.com:465’ smtp_from: ‘474785153@qq.com’ smtp_auth_username: ‘474785153@qq.com’ smtp_auth_password: ‘xxx’ # 这个密码是开启smtp授权后生成的,下文有说怎么配置 smtp_require_tls: falseroute: group_by: [‘alertmanager’,‘cluster’,‘service’] group_wait: 30s group_interval: 5m repeat_interval: 3h receiver: ‘fanux’ routes: - receiver: ‘fanux’receivers:- name: ‘fanux’ email_configs: - to: ‘474785153@qq.com’ send_resolved: truedelete掉老的secret,根据自己的配置重新生成secret即可kubectl delete secret alertmanager-main -n monitoringkubectl create secret generic alertmanager-main –from-file=alertmanager.yaml -n monitoring邮箱配置,以QQ邮箱为例开启smtp pop3服务 照着操作即可,后面会弹框一个授权码,配置到上面的配置文件中然后就可以收到告警了:告警规则配置prometheus operator自定义PrometheusRule crd去描述告警规则[root@dev-86-202 shell]# kubectl get PrometheusRule -n monitoringNAME AGEprometheus-k8s-rules 6m直接edit这个rule即可,也可以再自己去创建个PrometheusRulekubectl edit PrometheusRule prometheus-k8s-rules -n monitoring如我们在group里加一个告警:spec: groups: - name: ./example.rules rules: - alert: ExampleAlert expr: vector(1) - name: k8s.rules rules:重启prometheuspod:kubectl delete pod prometheus-k8s-0 prometheus-k8s-1 -n monitoring然后在界面上就可以看到新加的规则:探讨可加QQ群:98488045公众号: ...

January 3, 2019 · 2 min · jiezi

2018 小回顾

年底了,惯例做个小回顾,对这一年做个总结,也对下一年大致做个规划。不过今儿与往年不同的是昨晚突然发高烧,今儿都没能去上班,感谢我的小可爱在照顾我。这篇文章也是躺在床上用手机编辑的。还是按照惯例从工作,生活两方面来说。先聊聊工作。工作现在在网易有道负责 DevOPS 实践落地及 k8s 容器化平台和自动化平台的规划建设等。总体来说,现在的工作很开心,更能发挥我的所长,也遇到了不错的团队。说到现在负责的工作,如果大致有些了解的就会知道这个过程比较漫长,推进起来也会有各种阻力。毕竟要改变很多人的思想和习惯,我也在尽量让这一过程变的更加平滑。同时也在 push 一些理念到行业内,到社区中,不断的进行交流碰撞总结。社区贡献今年下半年的贡献和分享相比去年更多一些。主要的分享有:GITC - 《云原生时代下的 CI/CD 实践》PyCon China - 《基于 Docker 的 CI/CD 实践》DockerOne 社区 - 《基于 GitLab 的 CI 实践》Tech Talk Time - 《Docker 实战和基础架构》分享的主题基本都围绕在容器化和 CI/CD 方面,但每次分享内容却都不一样。感谢我的小可爱,也感谢所有支持的朋友们。社区中主要活跃在 Docker 和 Kubernetes 生态方向。维护一些官方镜像,做测试,解决问题,提交代码之类的,明年希望做的更多。开了一个知乎专栏 『k8s生态』 明年会花更多时间进行建设。写了一本掘金小册《Kubernetes 从上手到实践》。 其实这个名字并不能很好的概括小册里面的内容,其中也有源码分析之类的。要再次感谢小可爱,感谢编辑 Linmi ,感谢马达老板和何世友老板写的推荐语。也感谢所有人的支持,希望这本小册能对大家有所帮助。写小册的过程其实也蛮辛苦的,一般要么晚上写,写到凌晨 23 点,要么早上 56 点钟左右起床,写到去上班。尤其要感谢小可爱,给了我很多支持。生活总的来说是丰富多彩的一年。年初(其实是17年底,去HK是为了跨年)和小可爱一起去了香港,看了场杨千嬅的演唱会,回来后她写了几篇游记:HK游记(一)HK游记(二)HK游记(三)接下来去了成都,重庆,杭州:胖的这几个月与成都、重庆、杭州(一)胖的这几个月与成都、重庆、杭州(二)胖的这几个月与成都、重庆、杭州(三)之后我俩还去了苏州和南京,跟三九面基了下。再然后小可爱的爸妈来北京,我们一起逛了故宫,王府井之类的地儿。在这一年里小可爱的厨艺越来越棒,美滋滋,嘿嘿。(当然我也越来越胖)最后,希望明年能越来越好,实现继续在云原生领域的深耕;能跟小可爱越来越幸福。PS:今年比较冷,各位注意防寒保暖,免的像我一样生病了,身体很重要。

January 2, 2019 · 1 min · jiezi

Docker入门安装教程

Dokcer介绍:Docker是一种容器相关的技术,简单来说你可以把它当做一个微型的独立系统,在这个系统里面运行各种软件,然后又在linux服务器上或者是电脑上运行这个系统,这个系统就被称为Docker容器,而运行Docker容器的机器就被称为宿主机。Docker的特性使它可以原封不动的在不同的环境下移植运行,这就避免了同样的代码因为服务器上各种环境细微差别导致的BUG,而且Docker操作简单,易于上手。Docker组件:Docker客户端和服务器在Docker的官网上提供了对应各种不同系统所对应的客户端,用于操作Docker容器。Docker镜像镜像是Docker中最重要的一部分内容,上面介绍我们说了Docker可以在不同的环境下运行时也保证容器的一致性,这就是因为每个Docker容器都是基于Docker镜像创建的,就好比我们安装系统的时候需要使用镜像,而同一个镜像安装出来的系统又怎么可能会不一样呢?正是因为这个原因,只要是基于同一个Docker镜像生成的Docker容器必定是一模一样的环境,然后我们就可以在任何不同的宿主机上运行这个Docker容器,所以就保证了代码或者是应用程序不会因为不同的环境造成不同的BUG了。Registry专门用来存放各种镜像的仓库,我们可以使用Docker Hub这个公共的仓库,也可以自己搭建私有仓库,就和使用Github一样。Docker容器上面已经说过了,真正运行在宿主机上的是Docker容器,它们是基于Docker镜像生成的。Docker的安装:Docker支持Linux、mac和Windows系统,置于安装过程大家可以去查看官方的 Docker文档,也可以搜索别的教程,需要一点Linux基础。注意我们是安装的Docker CE,Docker EE是用于商业模式的。本文下面所有的例子都是使用的CentOS 7系统进行操作。检查配置是否满足条件 在CentOS下Docker必须保证内核版本是在3.8以上,可以使用uname -a查看,可以看到内核版本是3.10已经满足了要求。安装Device Mapper为Docker提供存储能力sudo yum install -y yum-utils \device-mapper-persistent-data \lvm2设置下载Docker CE的仓库地址sudo yum-config-manager --add-repo \https://download.docker.com/linux/centos/docker-ce.repo安装Docker客户端sudo yum install docker-ce查看Docker是否安装成功 我们可以输入sudo docker –version查看Docker版本,如果能成功显示版本号就表示安装成功,这里可以将docker添加到用户组里,就不需要每次都输入sudo了。启动Docker#以守护进程模式启动Dockersudo service docker start#设置开机自动启动Dockersystemctl start docker配置Docker镜像加速 因为Docker的站点是在国外,所以做一些网络操作的时候会比较慢,我们可以配置Docker的镜像加速器,这里我们使用阿里的镜像加速器。我们可以根据下面的操作文档执行命令。或者直接创建一个daemon.json文件放到/etc/docker目录下,内容如下,都可以达到同样的目的。 重启Dokcer之后可以使用docker info命令查看Docker客户端的信息,可以看到配置的镜像加速确实是起作用了。总结:这篇文章介绍了Docker的概念和用途,并演示了如何安装Docker,这里是使用的官网的安装教程,一些关于Docker的书籍或者其他Docker教程还会介绍一些别的安装方式,大家可以尝试一下,下篇文章会介绍Docker的基本使用。

January 1, 2019 · 1 min · jiezi

探索runC(下)

回顾本文接 探索runC(上) 前文讲到,newParentProcess() 根据源自 config.json 的配置,最终生成变量 initProcess ,这个 initProcess 包含的信息主要有cmd 记录了要执行的可执行文件名,即 “/proc/self/exe init”,注意不要和容器要执行的 sleep 5 混淆了cmd.Env 记录了名为 _LIBCONTAINER_FIFOFD=%d 记录的命名管道exec.fifo 的描述符,名为_LIBCONTAINER_INITPIPE=%d记录了创建的 SocketPair 的 childPipe 一端的描述符,名为_LIBCONTAINER_INITTYPE=“standard"记录要创建的容器中的进程是初始进程initProcess 的 bootstrapData 记录了新的容器要创建哪些类型的 Namespace。/* libcontainer/container_linux.go */func (c linuxContainer) start(process Process) error { parent, err := c.newParentProcess(process) / 1. 创建parentProcess (已完成) / err := parent.start(); / 2. 启动这个parentProcess / ……准备工作完成之后,就要调用 start() 方法启动。注意: 此时 sleep 5 线索存储在变量 parent 中runC create的实现原理 (下)start() 函数实在太长了,因此逐段来看/ libcontainer/process_linux.go /func (p initProcess) start() error { p.cmd.Start() p.process.ops = p io.Copy(p.parentPipe, p.bootstrapData) …..}p.cmd.Start() 启动 cmd 中设置的要执行的可执行文件 /proc/self/exe,参数是 init,这个函数会启动一个新的进程去执行该命令,并且不会阻塞。io.Copy 将 p.bootstrapData 中的数据通过 p.parentPipe 发送给子进程/proc/self/exe 正是runc程序自己,所以这里相当于是执行runc init,也就是说,我们输入的是runc create命令,隐含着又去创建了一个新的子进程去执行runc init。为什么要额外重新创建一个进程呢?原因是我们创建的容器很可能需要运行在一些独立的 namespace 中,比如 user namespace,这是通过 setns() 系统调用完成的,而在setns man page中写了下面一段话A multi‐threaded process may not change user namespace with setns(). It is not permitted to use setns() to reenter the caller’s current user names‐pace即多线程的进程是不能通过 setns()改变user namespace的。而不幸的是 Go runtime 是多线程的。那怎么办呢 ?所以setns()必须要在Go runtime 启动之前就设置好,这就要用到cgo了,在Go runtime 启动前首先执行嵌入在前面的 C 代码。具体的做法在nsenter README描述 在runc init命令的响应在文件 init.go 开头,导入 nsenter 包/ init.go /import ( “os” “runtime” “github.com/opencontainers/runc/libcontainer” _ “github.com/opencontainers/runc/libcontainer/nsenter” “github.com/urfave/cli”)而nsenter包中开头通过 cgo 嵌入了一段 C 代码, 调用 nsexec()package nsenter// nsenter.go /#cgo CFLAGS: -Wallextern void nsexec();void attribute((constructor)) init(void) { nsexec();}/import “C"接下来,轮到 nsexec() 完成为容器创建新的 namespace 的工作了, nsexec() 同样很长,逐段来看/ libcontainer/nsenter/nsexec.c /void nsexec(void){ int pipenum; jmp_buf env; int sync_child_pipe[2], sync_grandchild_pipe[2]; struct nlconfig_t config = { 0 }; / * If we don’t have an init pipe, just return to the go routine. * We’ll only get an init pipe for start or exec. / pipenum = initpipe(); if (pipenum == -1) return; / Parse all of the netlink configuration. / nl_parse(pipenum, &config); …… 上面这段 C 代码中,initpipe() 从环境中读取父进程之前设置的pipe(_LIBCONTAINER_INITPIPE记录的的文件描述符),然后调用 nl_parse 从这个管道中读取配置到变量 config ,那么谁会往这个管道写配置呢 ? 当然就是runc create父进程了。父进程通过这个pipe,将新建容器的配置发给子进程,这个过程如下图所示:发送的具体数据在 linuxContainer 的 bootstrapData() 函数中封装成netlink msg格式的消息。忽略大部分配置,本文重点关注namespace的配置,即要创建哪些类型的namespace,这些都是源自最初的config.json文件。至此,子进程就从父进程处得到了namespace的配置,继续往下, nsexec() 又创建了两个socketpair,从注释中了解到,这是为了和它自己的子进程和孙进程进行通信。void nsexec(void){ ….. / Pipe so we can tell the child when we’ve finished setting up. / if (socketpair(AF_LOCAL, SOCK_STREAM, 0, sync_child_pipe) < 0) // sync_child_pipe is an out parameter bail(“failed to setup sync pipe between parent and child”); / * We need a new socketpair to sync with grandchild so we don’t have * race condition with child. */ if (socketpair(AF_LOCAL, SOCK_STREAM, 0, sync_grandchild_pipe) < 0) bail(“failed to setup sync pipe between parent and grandchild”); }然后就该创建namespace了,看注释可知这里其实有考虑过三个方案first clone then clonefirst unshare then clonefirst clone then unshare最终采用的是方案 3,其中缘由由于考虑因素太多,所以准备之后另写一篇文章分析接下来就是一个大的 switch case 编写的状态机,大体结构如下,当前进程通过clone()系统调用创建子进程,子进程又通过clone()系统调用创建孙进程,而实际的创建/加入namespace是在子进程完成的switch (setjmp(env)) { case JUMP_PARENT:{ ….. clone_parent(&env, JUMP_CHILD); ….. } case JUMP_CHILD:{ …… if (config.namespaces) join_namespaces(config.namespaces); clone_parent(&env, JUMP_INIT); …… } case JUMP_INIT:{ }本文不准备展开分析这个状态机了,而将这个状态机的流程画在了下面的时序图中,需要注意的是以下几点namespaces在runc init 2完成创建runc init 1和runc init 2最终都会执行exit(0),但runc init 3不会,它会继续执行runc init命令的后半部分。因此最终只会剩下runc create进程和runc init 3进程再回到runc create进程func (p initProcess) start() error { p.cmd.Start() p.process.ops = p io.Copy(p.parentPipe, p.bootstrapData); p.execSetns() ……再向 runc init发送了 bootstrapData 数据后,便调用 execSetns() 等待runc init 1进程终止,从管道中得到runc init 3的进程 pid,将该进程保存在 p.process.ops/ libcontainer/process_linux.go */func (p *initProcess) execSetns() error { status, err := p.cmd.Process.Wait() var pid *pid json.NewDecoder(p.parentPipe).Decode(&pid) process, err := os.FindProcess(pid.Pid) p.cmd.Process = process p.process.ops = p return nil}继续 start()func (p *initProcess) start() error { …… p.execSetns() fds, err := getPipeFds(p.pid()) p.setExternalDescriptors(fds) p.createNetworkInterfaces() p.sendConfig() parseSync(p.parentPipe, func(sync syncT) error { switch sync.Type { case procReady: ….. writeSync(p.parentPipe, procRun); sentRun = true case procHooks: ….. // Sync with child. err := writeSync(p.parentPipe, procResume); sentResume = true } return nil }) ……可以看到,runc create又开始通过pipe进行双向通信了,通信的对端自然就是runc init 3进程了,runc init 3进程在执行完嵌入的 C 代码后(实际是runc init 1执行的,但runc init 3也是由runc init 1间接clone()出来的),因此将开始运行 Go runtime,开始响应init命令sleep 5 通过 p.sendConfig() 发送给了runc init进程init命令首先通过 libcontainer.New(””) 创建了一个 LinuxFactory,这个方法在上篇文章中分析过,这里不再解释。然后调用 LinuxFactory 的 StartInitialization() 方法。/ libcontainer/factory_linux.go */// StartInitialization loads a container by opening the pipe fd from the parent to read the configuration and state// This is a low level implementation detail of the reexec and should not be consumed externallyfunc (l LinuxFactory) StartInitialization() (err error) { var ( pipefd, fifofd int envInitPipe = os.Getenv("_LIBCONTAINER_INITPIPE") envFifoFd = os.Getenv("_LIBCONTAINER_FIFOFD") ) // Get the INITPIPE. pipefd, err = strconv.Atoi(envInitPipe) var ( pipe = os.NewFile(uintptr(pipefd), “pipe”) it = initType(os.Getenv("_LIBCONTAINER_INITTYPE")) // // “standard” or “setns” ) // Only init processes have FIFOFD. fifofd = -1 if it == initStandard { if fifofd, err = strconv.Atoi(envFifoFd); err != nil { return fmt.Errorf(“unable to convert _LIBCONTAINER_FIFOFD=%s to int: %s”, envFifoFd, err) } } i, err := newContainerInit(it, pipe, consoleSocket, fifofd) // If Init succeeds, syscall.Exec will not return, hence none of the defers will be called. return i.Init() //}StartInitialization() 方法尝试从环境中读取一系列_LIBCONTAINER_XXX变量的值,还有印象吗?这些值全是在runc create命令中打开和设置的,也就是说,runc create通过环境变量,将这些参数传给了子进程runc init 3拿到这些环境变量后,runc init 3调用 newContainerInit 函数/ libcontainer/init_linux.go */func newContainerInit(t initType, pipe *os.File, consoleSocket *os.File, fifoFd int) (initer, error) { var config initConfig / read config from pipe (from runc process) / son.NewDecoder(pipe).Decode(&config); populateProcessEnvironment(config.Env); switch t { …… case initStandard: return &linuxStandardInit{ pipe: pipe, consoleSocket: consoleSocket, parentPid: unix.Getppid(), config: config, // <=== config fifoFd: fifoFd, }, nil } return nil, fmt.Errorf(“unknown init type %q”, t)}newContainerInit() 函数首先尝试从 pipe 读取配置存放到变量 config 中,再存储到变量 linuxStandardInit 中返回 runc create runc init 3 | | p.sendConfig() — config –> NewContainerInit()sleep 5 线索在 initStandard.config 中回到 StartInitialization(),在得到 linuxStandardInit 后,便调用其 Init()方法了/ init.go */func (l *LinuxFactory) StartInitialization() (err error) { …… i, err := newContainerInit(it, pipe, consoleSocket, fifofd) return i.Init() }本文忽略掉 Init() 方法前面的一大堆其他配置,只看其最后func (l *linuxStandardInit) Init() error { …… name, err := exec.LookPath(l.config.Args[0]) syscall.Exec(name, l.config.Args[0:], os.Environ())}可以看到,这里终于开始执行 用户最初设置的 sleep 5 了 ...

December 31, 2018 · 4 min · jiezi

使用Docker开发Django项目

背景当多个Python项目且某些包无法兼容时,通常我们使用虚拟环境即可解决。但是在团队中多个环境其实相对比较固定了,较少变更,如果换电脑或者新人加入需要重新一个一个配置虚拟环境并安装相应的包,会耗费很多时间,而且由于重新安装的包依赖可能会有版本变更导致各种离奇问题。但事实上Docker不仅仅只能用于线上应用部署,我们的开发、调试环境也可以使用。下面以Django项目来举例,为了说明方便此处有以下前提条件和假设:基础Docker已经安装且可用Docker已经暴露了远程访问地址(使用Pycharm需要),具体方法请自行查阅文档或教材,假如为tcp://localhost:2375Docker基本命令不再详述Docker的Django环境镜像已经做好,为:myimageDjango代码目录为d:\demo演示环境为Windows 10(由于Docker集成原因,本文不适用于windows 10之前版本系统),linux和mac os可能稍有差别开始使用普通环境:python d:\demo\manage.py runserver 0.0.0.0:8000Docker启动:docker run -it –name demo -v d:\demo:/code -p 0.0.0.0:8000:8000 myimage python /code/manage.py runserver 0.0.0.0:8000在Pycharm中无缝使用添加Docker镜像:打开配置 pycharm > File > Settings > Project > Project Interpreter选择镜像修改原Run配置:打开原Run配置(和使用本地环境的配置一样,不再赘述)选择刚才添加的镜像,下面三个复选框保持下图一样上一步选择镜像后下面会出现Docker container settings:点开进行编辑,可以看到此时已经有了Volume bindings,还需要一个端口映射再次Run就已经是从容器中启动了(可以看到容器ID),使用Debug启动也是可以的底部还有个选项卡,此可以一键打开Django shell问题Q:使用Python Console打开django shell报错,错误示例:ModuleNotFoundError: No module named ‘cms’A:Pycharm > Settings > Build, Execution, Deployment > Console > Django Console 勾选如下两项,重新打开底部Python Console即可

December 28, 2018 · 1 min · jiezi

Spring、Spring Boot和TestNG测试指南 - 集成测试中用Docker创建数据库

原文地址在测试关系型数据库一篇里我们使用的是H2数据库,这是为了让你免去你去安装/配置一个数据库的工作,能够尽快的了解到集成测试的过程。在文章里也说了:在真实的开发环境中,集成测试用数据库应该和最终的生产数据库保持一致那么很容易就能想到两种解决方案:开发团队使用共用同一个数据库。这样做的问题在于:当有多个集成测试同时在跑时,会产生错误的测试结果。每个人使用自己的数据库。这样做的问题在于让开发人员维护MySQL数据库挺麻烦的。那么做到能否这样呢?测试启动前,创建一个MySQL数据库测试过程中连接到这个数据库测试结束后,删除这个MySQL数据库So, Docker comes to the rescue。我们还是会以测试关系型数据库里的FooRepositoryImpl来做集成测试(代码在这里)。下面来讲解具体步骤:安装Docker请查阅官方文档。并且掌握Docker的基本概念。配置fabric8 docker-maven-pluginfarbic8 docker-maven-plugin顾名思义就是一个能够使用docker的maven plugin。它主要功能有二:创建Docker image启动Docker container我们这里使用启动Docker container的功能。大致配置如下 <plugin> <groupId>io.fabric8</groupId> <artifactId>docker-maven-plugin</artifactId> <version>0.28.0</version> <configuration> <images> <image> <!– 使用mysql:8 docker image –> <name>mysql:8</name> <!– 定义docker run mysql:8 时的参数 –> <run> <ports> <!– host port到container port的映射 这里随机选择一个host port,并将值存到property docker-mysql.port里 –> <port>docker-mysql.port:3306</port> </ports> <!– 启动时给的环境变量,参阅文档:https://hub.docker.com/_/mysql –> <env> <MYSQL_ROOT_PASSWORD>123456</MYSQL_ROOT_PASSWORD> <MYSQL_DATABASE>test</MYSQL_DATABASE> <MYSQL_USER>foo</MYSQL_USER> <MYSQL_PASSWORD>bar</MYSQL_PASSWORD> </env> <!– 设置判定container启动成功的的条件及timeout –> <wait> <!– 如果container打出了这行日志,则说明容器启动成功 –> <log>MySQL init process done. Ready for start up.</log> <time>120000</time> </wait> </run> </image> </images> </configuration> <executions> <execution> <!– 在集成测试开始前启动容器 –> <id>start</id> <phase>pre-integration-test</phase> <goals> <goal>start</goal> </goals> </execution> <execution> <!– 在集成测试结束后停止并删除容器 –> <id>stop</id> <phase>post-integration-test</phase> <goals> <goal>stop</goal> </goals> </execution> </executions> </plugin>配置maven-failsafe-plugin<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <executions> <execution> <id>integration-test</id> <goals> <goal>integration-test</goal> </goals> </execution> <execution> <id>verify</id> <goals> <goal>verify</goal> </goals> </execution> </executions> <configuration> <!– 我们被测的是一个Spring Boot项目,因此可以通过System Properties把MySQL container的相关信息传递给程序 详见文档:https://docs.spring.io/spring-boot/docs/1.5.4.RELEASE/reference/html/boot-features-external-config.html –> <systemPropertyVariables> <spring.datasource.url>jdbc:mysql://localhost:${docker-mysql.port}/test</spring.datasource.url> <spring.datasource.username>foo</spring.datasource.username> <spring.datasource.password>bar</spring.datasource.password> </systemPropertyVariables> </configuration></plugin>执行三种常见用法:mvn clean integration-test,会启动docker container、运行集成测试。这个很有用,如果集成测试失败,那么你还可以连接到MySQL数据库查看情况。mvn clean verify,会执行mvn integration-test、删除docker container。mvn clean install,会执mvn verify,并将包安装到本地maven 仓库。下面是mvn clean verify的日志:…[INFO] — docker-maven-plugin:0.28.0:start (start) @ spring-test-examples-rdbs-docker —[INFO] DOCKER> [mysql:8]: Start container f683aadfe8ba[INFO] DOCKER> Pattern ‘MySQL init process done. Ready for start up.’ matched for container f683aadfe8ba[INFO] DOCKER> [mysql:8]: Waited on log out ‘MySQL init process done. Ready for start up.’ 13717 ms[INFO][INFO] — maven-failsafe-plugin:2.22.1:integration-test (integration-test) @ spring-test-examples-rdbs-docker —[INFO][INFO] ——————————————————-[INFO] T E S T S[INFO] ——————————————————-…[INFO][INFO] Results:[INFO][INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0[INFO][INFO][INFO] — docker-maven-plugin:0.28.0:stop (stop) @ spring-test-examples-rdbs-docker —[INFO] DOCKER> [mysql:8]: Stop and removed container f683aadfe8ba after 0 ms[INFO][INFO] — maven-failsafe-plugin:2.22.1:verify (verify) @ spring-test-examples-rdbs-docker —[INFO] ————————————————————————[INFO] BUILD SUCCESS[INFO] ————————————————————————…可以看到fabric8 dmp在集成测试前后start和stop容器的相关日志,且测试成功。如何找到MySQL的端口开在哪一个呢?运行docker ps查看端口(注意下面的0.0.0.0:32798->3306/tcp):CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESa1f4b51d7c75 mysql:8 … … Up 19… 33060/tcp, 0.0.0.0:32798->3306/tcp mysql-1参考文档Fabric8 dmpSpring boot - Externalized Configuration ...

December 28, 2018 · 2 min · jiezi

探索 runC (上)

前言容器运行时(Container Runtime)是指管理容器和容器镜像的软件。当前业内比较有名的有docker,rkt等。如果不同的运行时只能支持各自的容器,那么显然不利于整个容器技术的发展。于是在2015年6月,由Docker以及其他容器领域的领导者共同建立了围绕容器格式和运行时的开放的工业化标准,即Open Container Initiative(OCI),OCI具体包含两个标准:运行时标准(runtime-spec)和容器镜像标准(image-spec)。简单来说,容器镜像标准定义了容器镜像的打包形式(pack format),而运行时标准定义了如何去运行一个容器。本文包含以下内容:runC的概念和使用runC运行容器的原理剖析本文不包含以下内容:docker engine使用runCrunC概念runC是一个遵循OCI标准的用来运行容器的命令行工具(CLI Tool),它也是一个Runtime的实现。尽管你可能对这个概念很陌生,但实际上,你的电脑上的docker底层可能正在使用它。至少在笔者的主机上是这样。root@node-1:~# docker info…..Runtimes: runcDefault Runtime: runc …..安装runCrunC不仅可以被docker engine使用,它也可以单独使用(它本身就是命令行工具),以下使用步骤完全来自runC’s README,如果依赖项Go version 1.6或更高版本libseccomp库 yum install libseccomp-devel for CentOS apt-get install libseccomp-dev for Ubuntu下载编译# 在GOPATH/src目录创建’github.com/opencontainers’目录> cd github.com/opencontainers> git clone https://github.com/opencontainers/runc> cd runc> make> sudo make install或者使用go get安装# 在GOPATH/src目录创建github.com目录> go get github.com/opencontainers/runc> cd $GOPATH/src/github.com/opencontainers/runc> make> sudo make install以上步骤完成后,runC将安装在/usr/local/sbin/runc目录使用runC创建一个OCI BundleOCI Bundle是指满足OCI标准的一系列文件,这些文件包含了运行容器所需要的所有数据,它们存放在一个共同的目录,该目录包含以下两项:config.json:包含容器运行的配置数据container 的 root filesystem如果主机上安装了docker,那么可以使用docker export命令将已有镜像导出为OCI Bundle的格式# create the top most bundle directory> mkdir /mycontainer> cd /mycontainer# create the rootfs directory> mkdir rootfs# export busybox via Docker into the rootfs directory> docker export $(docker create busybox) | tar -C rootfs -xvf -> ls rootfs bin dev etc home proc root sys tmp usr var有了root filesystem,还需要config.json,runc spec可以生成一个基础模板,之后我们可以在模板基础上进行修改。> runc spec> lsconfig.json rootfs生成的config.json模板比较长,这里我将它process中的arg 和 terminal进行修改{ “process”: { “terminal”:false, <– 这里改为 true “user”: { “uid”: 0, “gid”: 0 }, “args”: [ “sh” <– 这里改为 “sleep”,“5” ], “env”: [ “PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin”, “TERM=xterm” ], “cwd”: “/”, }, “root”: { “path”: “rootfs”, “readonly”: true }, “linux”: { “namespaces”: [ { “type”: “pid” }, { “type”: “network” }, { “type”: “ipc” }, { “type”: “uts” }, { “type”: “mount” } ], }} config.json 文件的内容都是 OCI Container Runtime 的订制,其中每一项值都可以在Runtime Spec找到具体含义,OCI Container Runtime 支持多种平台,因此其 Spec 也分为通用部分(在config.md中描述)以及平台相关的部分(如linux平台上就是config-linux)process:指定容器启动后运行的进程运行环境,其中最重要的的子项就是args,它指定要运行的可执行程序, 在上面的修改后的模板中,我们将其改成了"sleep 5"root:指定容器的根文件系统,其中path子项是指向前面导出的中root filesystem的路径linux: 这一项是平台相关的。其中namespaces表示新创建的容器会额外创建或使用的namespace的类型运行容器现在我们使用create命令创建容器# run as root> cd /mycontainer> runc create mycontainerid使用list命令查看容器状态为created# view the container is created and in the “created” state> runc listID PID STATUS BUNDLE CREATED OWNERmycontainerid 12068 created /mycontainer 2018-12-25T19:45:37.346925609Z root 使用start命令查看容器状态# start the process inside the container> runc start mycontainerid在5s内 使用list命令查看容器状态为running# within 5 seconds view that the container is runningrunc listID PID STATUS BUNDLE CREATED OWNERmycontainerid 12068 running /mycontainer 2018-12-25T19:45:37.346925609Z root 在5s后 使用list命令查看容器状态为stopped# after 5 seconds view that the container has exited and is now in the stopped staterunc listID PID STATUS BUNDLE CREATED OWNERmycontainerid 0 stopped /mycontainer 2018-12-25T19:45:37.346925609Z root 使用delete命令可以删除容器# now delete the containerrunc delete mycontaineridrunC 的实现原理runC可以启动并管理符合OCI标准的容器。简单地说,runC需要利用OCI bundle创建一个独立的运行环境,并执行指定的程序。在Linux平台上,这个环境就是指各种类型的Namespace以及Capability等等配置代码结构runC由Go语言实现,当前(2018.12)最新版本是v1.0.0-rc6,代码的结构可分为两大块,一是根目录下的go文件,对应各个runC命令,二是负责创建/启动/管理容器的libcontainer,可以说runC的本质都在libcontainerrunc create的过程以上面的例子为例,以’runc create’这条命令来看runC是如何完成从无到有创建容器create命令的响应入口在 create.go, 我们直接关注其注册的Action的实现,当输入runc create mycontainerid时会执行注册的Action,并且参数存放在Context中/* run.go */Action: func(context *cli.Context) error { …… spec, err := setupSpec(context) status, err := startContainer(context, spec, CT_ACT_CREATE, nil) …..}setupSpec:从命令行输入中找到-b 指定的 OCI bundle 目录,若没有此参数,则默认是当前目录。读取config.json文件,将其中的内容转换为Go的数据结构specs.Spec,该结构定义在文件 github.com/opencontainers/runtime-spec/specs-go/config.go,里面的内容都是OCI标准描述的startContainer:尝试创建启动容器,注意这里的第三个参数是 CT_ACT_CREATE, 表示仅创建容器。本文使用linux平台,因此实际调用的是 utils_linux.go 中的startContainer()。startContainer()根据用户将用户输入的 id 和刚才的得到的 spec 作为输入,调用 createContainer() 方法创建容器,再通过一个runner.run()方法启动它/× utils_linux.go ×/func startContainer(context *cli.Context, spec *specs.Spec, action CtAct, criuOpts libcontainer.CriuOpts) (int, error) { id := context.Args().First() container, err := createContainer(context, id, spec) r := &runner{ container: container, action: action, init: true, …… } return r.run(spec.Process)}这里需要先了解下runC中的几个重要数据结构的关系Container 接口在runC中,Container用来表示一个容器对象,它是一个抽象接口,它内部包含了BaseContainer接口。从其内部的方法的名字就可以看出,都是管理容器的基本操作/ libcontainer/container.go */type BaseContainer interface { ID() string Status() (Status, error) State() (*State, error) Config() configs.Config Processes() ([]int, error) Stats() (*Stats, error) Set(config configs.Config) error Start(process *Process) (err error) Run(process Process) (err error) Destroy() error Signal(s os.Signal, all bool) error Exec() error}/ libcontainer/container_linux.go */type Container interface { BaseContainer Checkpoint(criuOpts *CriuOpts) error Restore(process *Process, criuOpts *CriuOpts) error Pause() error Resume() error NotifyOOM() (<-chan struct{}, error) NotifyMemoryPressure(level PressureLevel) (<-chan struct{}, error)}有了抽象接口,那么一定有具体的实现,linuxContainer 就是一个实现,或者说,它是当前版本runC在linux平台上的唯一一种实现。下面是其定义,其中的 initPath 非常关键type linuxContainer struct { id string config *configs.Config initPath string initArgs []string initProcess parentProcess …..}Factory 接口在runC中,所有的容器都是由容器工厂(Factory)创建的, Factory 也是一个抽象接口,定义如下,它只包含了4个方法type Factory interface { Create(id string, config *configs.Config) (Container, error) Load(id string) (Container, error) StartInitialization() error Type() string}linux平台上的对 Factory 接口也有一个标准实现—LinuxFactory,其中的 InitPath 也非常关键,稍后我们会看到// LinuxFactory implements the default factory interface for linux based systems.type LinuxFactory struct { // InitPath is the path for calling the init responsibilities for spawning // a container. InitPath string …… // InitArgs are arguments for calling the init responsibilities for spawning // a container. InitArgs []string}所以,对于linux平台,Factory 创建 Container 实际上就是 LinuxFactory 创建 linuxContainer回到createContainer(),下面是其实现func createContainer(context *cli.Context, id string, spec specs.Spec) (libcontainer.Container, error) { / 1. 将配置存放到config / rootlessCg, err := shouldUseRootlessCgroupManager(context) config, err := specconv.CreateLibcontainerConfig(&specconv.CreateOpts{ CgroupName: id, UseSystemdCgroup: context.GlobalBool(“systemd-cgroup”), NoPivotRoot: context.Bool(“no-pivot”), NoNewKeyring: context.Bool(“no-new-keyring”), Spec: spec, RootlessEUID: os.Geteuid() != 0, RootlessCgroups: rootlessCg, }) / 2. 加载Factory / factory, err := loadFactory(context) if err != nil { return nil, err } / 3. 调用Factory的Create()方法 / return factory.Create(id, config)}可以看到,上面的代码大体上分为将配置存放到config加载Factory调用Factory的Create()方法第1步存放配置没什么好说的,无非是将已有的 spec 和其他一些用户命令行选项配置换成一个数据结构存下来。而第2部加载Factory,在linux上,就是返回一个 LinuxFactory 结构。而这是通过在其内部调用 libcontainer.New()方法实现的/ utils/utils_linux.go */func loadFactory(context cli.Context) (libcontainer.Factory, error) { ….. return libcontainer.New(abs, cgroupManager, intelRdtManager, libcontainer.CriuPath(context.GlobalString(“criu”)), libcontainer.NewuidmapPath(newuidmap), libcontainer.NewgidmapPath(newgidmap))}libcontainer.New() 方法在linux平台的实现如下,可以看到,它的确会返回一个LinuxFactory,并且InitPath设置为"/proc/self/exe",InitArgs设置为"init"/ libcontainer/factory_linux.go */func New(root string, options …func(*LinuxFactory) error) (Factory, error) { ….. l := &LinuxFactory{ ….. InitPath: “/proc/self/exe”, InitArgs: []string{os.Args[0], “init”}, } …… return l, nil}得到了具体的 Factory 实现,下一步就是调用其Create()方法,对 linux 平台而言,就是下面这个方法,可以看到,它会将 LinuxFactory 上记录的 InitPath 和 InitArgs 赋给 linuxContainer 并作为结果返回func (l *LinuxFactory) Create(id string, config configs.Config) (Container, error) { …. c := &linuxContainer{ id: id, config: config, initPath: l.InitPath, initArgs: l.InitArgs, } ….. return c, nil}回到 startContainer() 方法,再得到 linuxContainer 后,将创建一个 runner 结构,并调用其run()方法/ utils_linux.go */func startContainer(context *cli.Context, spec *specs.Spec, action CtAct, criuOpts *libcontainer.CriuOpts) (int, error) { id := context.Args().First() container, err := createContainer(context, id, spec) r := &runner{ container: container, action: action, init: true, …… } return r.run(spec.Process)}runner 的 run() 的入参是 spec.Process 结构,我们并不需要关注它的定义,因为它的内容都来源于 config.json 文件,spec.Process 不过是其中 Process 部分的 Go 语言数据的表示。run() 方法的实现如下:func (r *runner) run(config *specs.Process) (int, error) { …… process, err := newProcess(config, r.init) …… switch r.action { case CT_ACT_CREATE: err = r.container.Start(process) / runc start / case CT_ACT_RESTORE: err = r.container.Restore(process, r.criuOpts) / runc restore / case CT_ACT_RUN: err = r.container.Run(process) / runc run / default: panic(“Unknown action”) } …… return status, err}上面的 run() 可分为两部分调用 newProcess() 方法, 用 spec.Process 创建 libcontainer.Process,注意第二个参数是 true ,表示新创建的 process 会作为新创建容器的第一个 process根据 r.action 的值决定如何操作得到的 libcontainer.Processlibcontainer.Process 结构定义在 /libcontainer/process.go, 其中大部分内容都来自 spec.Process/ parent process */// Process specifies the configuration and IO for a process inside// a container.type Process struct { Args []string Env []string User string AdditionalGroups []string Cwd string Stdin io.Reader Stdout io.Writer Stderr io.Writer ExtraFiles []*os.File ConsoleWidth uint16 ConsoleHeight uint16 Capabilities *configs.Capabilities AppArmorProfile string Label string NoNewPrivileges *bool Rlimits []configs.Rlimit ConsoleSocket *os.File Init bool ops processOperations}接下来就是要使用 Start() 方法了func (c *linuxContainer) Start(process Process) error { if process.Init { if err := c.createExecFifo(); err != nil { / 1.创建fifo / return err } } if err := c.start(process); err != nil { / 2. 调用start() */ if process.Init { c.deleteExecFifo() } return err } return nil}Start() 方法主要完成两件事创建 fifo: 创建一个名为exec.fifo的管道,这个管道后面会用到调用 start() 方法,如下func (c *linuxContainer) start(process Process) error { parent, err := c.newParentProcess(process) / 1. 创建parentProcess / err := parent.start(); / 2. 启动这个parentProcess */ …… start() 也完成两件事:创建一个 ParentProcess调用这个 ParentProcess 的 start() 方法那么什么是 parentProcess ? 正如其名,parentProcess 类似于 linux 中可以派生出子进程的父进程,在runC中,parentProcess 是一个抽象接口,如下:type parentProcess interface { // pid returns the pid for the running process. pid() int // start starts the process execution. start() error // send a SIGKILL to the process and wait for the exit. terminate() error // wait waits on the process returning the process state. wait() (*os.ProcessState, error) // startTime returns the process start time. startTime() (uint64, error) signal(os.Signal) error externalDescriptors() []string setExternalDescriptors(fds []string)}它有两个实现,分别为 initProcess 和 setnsProcess ,前者用于创建容器内的第一个进程,后者用于在已有容器内创建新的进程。在我们的创建容器例子中,p.Init = true ,所以会创建 initProcessfunc (c *linuxContainer) newParentProcess(p Process) (parentProcess, error) { parentPipe, childPipe, err := utils.NewSockPair(“init”) / 1.创建 Socket Pair / cmd, err := c.commandTemplate(p, childPipe) / 2. 创建 *exec.Cmd / if !p.Init { return c.newSetnsProcess(p, cmd, parentPipe, childPipe) } if err := c.includeExecFifo(cmd); err != nil { / 3.打开之前创建的fifo / return nil, newSystemErrorWithCause(err, “including execfifo in cmd.Exec setup”) } return c.newInitProcess(p, cmd, parentPipe, childPipe) / 4.创建 initProcess */}newParentProcess() 方法动作有 4 步,前 3 步都是在为第 4 步做准备,即生成 initProcess创建一对 SocketPair 没什么好说的,生成的结果会放到 initProcess创建 *exec.Cmd,代码如下,这里设置了 cmd 要执行的可执行程序和参数来自 c.initPath,即源自 LInuxFactory 的 “/proc/self/exe”,和 “init” ,这表示新执行的程序就是runC本身,只是参数变成了 init,之后又将外面创建的 SocketPair 的一端 childPipe放到了cmd.ExtraFiles ,同时将_LIBCONTAINER_INITPIPE=%d加入cmd.Env,其中 %d为文件描述符的数字func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.Cmd, error) { cmd := exec.Command(c.initPath, c.initArgs[1:]…) cmd.Args[0] = c.initArgs[0] cmd.ExtraFiles = append(cmd.ExtraFiles, p.ExtraFiles…) cmd.ExtraFiles = append(cmd.ExtraFiles, childPipe) cmd.Env = append(cmd.Env, fmt.Sprintf("_LIBCONTAINER_INITPIPE=%d", stdioFdCount+len(cmd.ExtraFiles)-1), ) …… return cmd, nil}includeExecFifo() 方法打开之前创建的 fifo,也将其 fd 放到 cmd.ExtraFiles 中。最后就是创建 InitProcess 了,这里首先将_LIBCONTAINER_INITTYPE=“standard"加入cmd.Env,然后从 configs 读取需要新的容器创建的 Namespace 的类型,并将其打包到变量 data 中备用,最后再创建 InitProcess 自己,可以看到,这里将之前的一些资源和变量都联系了起来func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File) (*initProcess, error) { cmd.Env = append(cmd.Env, “_LIBCONTAINER_INITTYPE="+string(initStandard)) nsMaps := make(map[configs.NamespaceType]string) for _, ns := range c.config.Namespaces { if ns.Path != "” { nsMaps[ns.Type] = ns.Path } } _, sharePidns := nsMaps[configs.NEWPID] data, err := c.bootstrapData(c.config.Namespaces.CloneFlags(), nsMaps) if err != nil { return nil, err } return &initProcess{ cmd: cmd, childPipe: childPipe, parentPipe: parentPipe, manager: c.cgroupManager, intelRdtManager: c.intelRdtManager, config: c.newInitConfig(p), container: c, process: p, bootstrapData: data, sharePidns: sharePidns, }, nil}回到 linuxContainer 的 start() 方法,创建好了 parent ,下一步就是调用它的 start() 方法了func (c *linuxContainer) start(process Process) error { parent, err := c.newParentProcess(process) / 1. 创建parentProcess (已完成) / err := parent.start(); / 2. 启动这个parentProcess */ ……—– 待续 ...

December 27, 2018 · 6 min · jiezi

《Kubernetes从上手到实践》正式上线

时间飞逝,转眼今年又要结束了。感谢还在关注的小伙伴,今年确实更新很少,能不取关的都是真爱…今年发生了很多事情,留着过几天年终总结的时候再说。有很大一部分的休息时间都用来完成了我的第一本掘金小册 《Kubernetes 从上手到实践》小册已经正式上线,特意送上各位小伙伴一份礼物,小册 8 折优惠。直接扫码 或者点击此链接即可。以下是关于小册的一些介绍:随着容器化及微服务等概念的普及,各个公司都在围绕着如何打造生产环境可用的,高效的容器调度平台,应用快速部署,扩容等平台进行探索。Kubernetes 是 Google 在 2014 年基于其多年在 Borg 系统实践总结出的经验而开源出的一套标准化,可扩展的系统。而发展至现在(2018年)Kubernetes 已经基本成为了容器编排领域事实上的标准,并且大量的公司都已在生产中使用,无论是国外的 Google, Amazon, GitHub 等,还是国内的阿里,腾讯,京东,滴滴及其他中小公司都在进行着大量的探索及实践。之前在容器化尚未大量推进的时候,开发工程师只需要关注自己业务代码的实现,而运维工程师在反复的为部署,扩容所需的环境而费时费力。为了解决环境一致性的问题,也为了能够提高资源的利用率,容器化开始逐步推进,开发工程师的交付由原先的交付代码变成了交付镜像,运维工程师可以将精力集中于保障服务的可高用上。但为了能够快速的发版验证功能,不再受单体化架构的拖累,微服务的概念也在实践中逐步推进,从原先的单体集中式的服务,拆分为多个松耦合的微服务。到了这时,微服务 + 容器化已经大势所趋,生产中要大量使用,则容器编排变的愈发重要。Kubernetes 在容器编排领域目前已成为事实上的标准,大量公司均已在生产中推进,此时,无论是开发工程师还是运维工程师,皆需要了解并掌握 Kubernetes 的基础技能,才不至于丢失自己的竞争力。Kubernetes 所涉及的知识点很多, 并且版本迭代也很快,本小册将集中于 Kubernetes 的基础技能,以最常见 Case 入手,帮助大家更快的掌握相关知识并将其用于生产实践中。同时在此过程中,也会深入至 Kubernetes 必要的原理中,同时也会提供相关涉及到的 Docker 及 Linux 内核知识的补充,以便让大家不仅知其然,而且知其所以然。你会学到什么?Kubernetes 基础架构Kubernetes 的基础技能, 覆盖常见 Case从零搭建 Kubernetes 集群与 Kubernetes 相关的 Docker 和 Linux 内核知识补充深入 Kubernetes 组件的原理和源码解析了解 Kubernetes 进阶相关知识体系适宜人群了解 Docker,希望能进入 K8S 领域的各领域工程师;正在或即将在生产环境使用 K8S 的后端工程师;需要维护或在公司落地 K8S 的运维工程师;想要走在技术前沿的前端/后端/运维工程师;准备查缺补漏的容器相关开发工程师;

December 27, 2018 · 1 min · jiezi

被忽略的后台开发神器 — Docker

刚接触Docker的时候,以为只是用来做运维。后来真正用的时候才发觉,这个Docker简直是个神器。不管什么开发场景都能轻松应付。想要什么环境都能随意生成,而且灵活性更高,更轻量,完美实现微服务的概念。什么是DockerDocker是一个开源的应用容器引擎,基于Go语言 并遵从Apache2.0协议开源。传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。它占用的资源更少,能做到的事更多。与传统虚拟机的对比特性容器虚拟机启动秒级分钟级 硬盘启动一般为MB一般为GB性能接近原生弱于系统支持量单机支持上千个容器一般几十个安装Docker安装的方法都挺简单的,我用的是mac,直接通过Docker官网下载软件安装,全程无障碍。Docker概念镜像(images):Docker镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。(直白点可以理解为系统安装包)容器(container):镜像和容器的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。(可以理解为安装好的系统)Docker镜像使用一、下载镜像大概了解了Docker的概念以后,我们就尝试拉取flask镜像使用一下。查找镜像可以通过https://hub.docker.com/网站来搜索,或者通过命令搜索。docker search flask在这里,我是通过Docker hub官网挑选出了python3.7 + alpine3.8组合的运行环境,alpine是精简版的linux,体积更小、运行的资源消耗更少。# 拉取镜像docker pull tiangolo/uwsgi-nginx-flask:python3.7-alpine3.8# 下载好可查看镜像列表是否存在docker images二、运行flask镜像下载镜像以后,就开始运行下试试,感受一下Docker的轻量、快捷。首先创建个flask运行文件来,在这里,我创建了/docker/flask作为项目文件,然后在根目录下再创建个app文件夹来存放main.py文件,代码如下:from flask import Flaskapp = Flask(name)@app.route("/")def hello(): return “Hello World from Flask!“if name == “main”: # 测试环境下才开启debug模式 app.run(host=‘0.0.0.0’, debug=True, port=80)现在的文件结构:flask └── app └── main.py运行命令docker run -it –name test -p 8080:80 -v /docker/flask/app:/app -w /app tiangolo/uwsgi-nginx-flask:python3.7-alpine3.8 python main.py这里说明一下命令的参数含义:-it 是将-i -t合并起来,作用是可以用指定终端对容器执行命令交互。–name 对容器进行命名。-p 将主机的8080端口映射到容器的80端口。-v 将主机的/docker/flask/app文件挂载到容器的/app文件,如果容器内没有的话会自动创建。-w 将/app文件作为工作区,后面的执行命令都默认在该文件路径下执行。tiangolo/uwsgi-nginx-flask:python3.7-alpine3.8 镜像名跟标签。python main.py 通过python来运行工作区的main.py文件。运行结果:现在主机跟容器的链接已经建立起来了,主机通过8080端口就能访问到容器的网站。自定义镜像在使用别人定制的镜像时总是不能尽善尽美的,如果在自己项目里面,不能每次都是拉取下来重新配置一下。像上面的镜像,我可不喜欢这么长的名字,想想每次要敲这么长的名字都头疼(tiangolo/uwsgi-nginx-flask:python3.7-alpine3.8)。编写Dockerfile文件打开我们刚才的/docker/flask路径,在根目录下创建Dockerfile文件,内容如下。# 基础镜像FROM tiangolo/uwsgi-nginx-flask:python3.7-alpine3.8# 没有vim来查看文件很不习惯,利用alpine的包管理安装一个来RUN apk add vim# 顺便用pip安装个redis包,后面用得上RUN pip3 install redis# 将我们的app文件加入到自定义镜像里面去COPY ./app /app现在我们的文件结构是:flask├── app│ └── main.py└── Dockerfile剩下的就跑一遍就OK啦!记得一定要在Dockerfile文件同级目录下执行build命令。docker build -t myflask .Sending build context to Docker daemon 4.608kBStep 1/4 : FROM tiangolo/uwsgi-nginx-flask:python3.7-alpine3.8 —> c69984ff0683Step 2/4 : RUN apk add vim —> Using cache —> ebe2947fcf89Step 3/4 : RUN pip3 install redis —> Running in aa774ba9030eCollecting redis Downloading https://files.pythonhosted.org/packages/f5/00/5253aff5e747faf10d8ceb35fb5569b848cde2fdc13685d42fcf63118bbc/redis-3.0.1-py2.py3-none-any.whl (61kB)Installing collected packages: redisSuccessfully installed redis-3.0.1Removing intermediate container aa774ba9030e —> 47a0f1ce8ea2Step 4/4 : COPY ./app /app —> 50908f081641Successfully built 50908f081641Successfully tagged myflask:latest-t 指定要创建的目标路径。. 这里有个点记住啦,表示是当前路径下的Dockerfile文件,可以指定为绝对路径。编译完后就通过docker images查看一下,就能看到myflask镜像了,里面能直接运行python main.py来启动flask,并且内置了vim和redis包。Docker Compose让多容器成为一个整体我们的每个容器都负责一个服务,这样容器多的时候一个个手动启动的话是不现实的。在这种情况我们可以通过Docker Compose来关联每个容器,组成一个完整的项目。Compose项目由Python编写,实现上调用了 Docker服务提供的 API 来对容器进行管理。# 安装docker-composesudo pip3 install docker-compose实现能记录访问次数的web在这里,我们通过docker-compose.yml文件来启动flask容器和redis容器,并将两个不同容器相互关联起来。首先在/docker/flask目录下创建docker-compose.yml文件,内容如下:version: ‘3’services: flask: image: myflask container_name: myflask ports: - 8080:80 volumes: - /docker/flask/app:/app working_dir: /app # 运行后执行的命令 command: python main.py redis: # 如果没有这个镜像的话会自动下载 image: “redis:latest” container_name: myredis然后我们把上面的main.py代码修改一下,连接redis数据库并记录网站访问次数。main.py修改后内容如下:from flask import Flaskfrom redis import Redisapp = Flask(name)redis = Redis(host=‘redis’, port=6379)@app.route(”/")def hello(): count = redis.incr(‘visit’) return f"Hello World from Flask! 该页面已被访问{count}次。“if name == “main”: # Only for debugging while developing app.run(host=‘0.0.0.0’, debug=True, port=80)目前的文件结构是:flask├── app│ └── main.py└── Dockerfile└── docker-compose.yml这些编排的文件参数都是取自于Docker,基本都能看懂,其它就没啥啦,直接命令行跑起来:docker-compose up就辣么简单!现在我们在浏览器上访问http://localhost:8080/就能看到结果了,并且每访问一次这页面都会自动增加访问次数.在这里,我们也能通过docker ps命令查看运行中的容器:docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES66133318452d redis:latest “docker-entrypoint.s…” 13 seconds ago Up 12 seconds 6379/tcp myredis0956529c3c9c myflask “/entrypoint.sh pyth…” 13 seconds ago Up 11 seconds 443/tcp, 0.0.0.0:8080->80/tcp myflask有了Docker Compose的Docker才是完整的Docker,有了这些以后开发简直不要太爽,每个容器只要维护自己的服务环境就ok了。Docker的日常操作镜像常用操作# 下载镜像docker pull name# 列出本地镜像docker images# 使用镜像运行生成容器docker run name:tag# 删除镜像docker rmi id/name容器常用操作可以通过容器的id或者容器别名来启动、停止、重启。# 查看运行中的容器docker ps# 查看所有生成的容器docker ps -a# 开始容器docker start container# 停止容器docker stop container# 重启容器docker restart container# 移除不需要的容器(移除前容器必须要处于停止状态)docker rm container# 进入后台运行的容器docker exec -it container /bin/sh# 打印容器内部的信息(-f参数能实时观察内部信息)docker logs -f container通过-i -t进来容器的,可以先按ctrl + p, 然后按ctrl + q来退出交互界面组,这样退出不会关闭容器。docker-compose常用操作# 自动完成包括构建镜像,(重新)创建服务,启动服务,并关联服务相关容器的一系列操作。docker-compose up# 此命令将会停止 up 命令所启动的容器,并移除网络docker-compose down# 启动已经存在的服务容器。docker-compose start# 停止已经处于运行状态的容器,但不删除它。通过start可以再次启动这些容器。docker-compose stop# 重启项目中的服务docker-compose restart默认情况,docker-compose up启动的容器都在前台,控制台将会同时打印所有容器的输出信息,可以很方便进行调试。当通过Ctrl-C停止命令时,所有容器将会停止。结语这次接触Docker的时间虽然不长,但是这种微服务细分的架构真的是惊艳到我了。以前玩过VM虚拟机,那个使用成本太高,不够灵活,用过一段时间就放弃了,老老实实维护自己的本机环境。有了这个Docker以后,想要什么测试环境都行,直接几行代码生成就好,一种随心所欲的自由。上面写的那些都是日常使用的命令,能应付基本的需求了,真要深入的话建议去找详细的文档,我就不写太累赘了,希望大家都能去接触一下这个Docker,怎么都不亏,你们也会喜欢上这小鲸鱼的。 ...

December 27, 2018 · 2 min · jiezi

利用 Kubeadm部署 Kubernetes 1.13.1 集群实践录

概 述Kubernetes集群的搭建方法其实有多种,比如我在之前的文章《利用K8S技术栈打造个人私有云(连载之:K8S集群搭建)》中使用的就是二进制的安装方法。虽然这种方法有利于我们理解 k8s集群,但却过于繁琐。而 kubeadm是 Kubernetes官方提供的用于快速部署Kubernetes集群的工具,其历经发展如今已经比较成熟了,利用其来部署 Kubernetes集群可以说是非常好上手,操作起来也简便了许多,因此本文详细叙述之。注: 本文首发于 My Personal Blog:CodeSheep·程序羊,欢迎光临 小站节点规划本文准备部署一个 一主两从 的 三节点 Kubernetes集群,整体节点规划如下表所示:主机名IP角色 k8s-master192.168.39.79k8s主节点 k8s-node-1192.168.39.77k8s从节点 k8s-node-2192.168.39.78k8s从节点 下面介绍一下各个节点的软件版本:操作系统:CentOS-7.4-64BitDocker版本:1.13.1Kubernetes版本:1.13.1所有节点都需要安装以下组件:Docker:不用多说了吧kubelet:运行于所有 Node上,负责启动容器和 Podkubeadm:负责初始化集群kubectl: k8s命令行工具,通过其可以部署/管理应用 以及CRUD各种资源准备工作所有节点关闭防火墙systemctl disable firewalld.service systemctl stop firewalld.service禁用SELINUXsetenforce 0vi /etc/selinux/configSELINUX=disabled所有节点关闭 swapswapoff -a设置所有节点主机名hostnamectl –static set-hostname k8s-masterhostnamectl –static set-hostname k8s-node-1hostnamectl –static set-hostname k8s-node-2所有节点 主机名/IP加入 hosts解析编辑 /etc/hosts文件,加入以下内容:192.168.39.79 k8s-master192.168.39.77 k8s-node-1192.168.39.78 k8s-node-2组件安装0x01. Docker安装(所有节点)不赘述 ! ! !0x02. kubelet、kubeadm、kubectl安装(所有节点)首先准备repocat>>/etc/yum.repos.d/kubrenetes.repo<<EOF[kubernetes]name=Kubernetes Repobaseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/gpgcheck=0gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpgEOF然后执行如下指令来进行安装setenforce 0sed -i ’s/^SELINUX=enforcing$/SELINUX= disabled/’ /etc/selinux/configyum install -y kubelet kubeadm kubectlsystemctl enable kubelet && systemctl start kubeletMaster节点配置0x01. 初始化 k8s集群为了应对网络不畅通的问题,我们国内网络环境只能提前手动下载相关镜像并重新打 tag :docker pull mirrorgooglecontainers/kube-apiserver:v1.13.1docker pull mirrorgooglecontainers/kube-controller-manager:v1.13.1docker pull mirrorgooglecontainers/kube-scheduler:v1.13.1docker pull mirrorgooglecontainers/kube-proxy:v1.13.1docker pull mirrorgooglecontainers/pause:3.1docker pull mirrorgooglecontainers/etcd:3.2.24docker pull coredns/coredns:1.2.6docker pull registry.cn-shenzhen.aliyuncs.com/cp_m/flannel:v0.10.0-amd64docker tag mirrorgooglecontainers/kube-apiserver:v1.13.1 k8s.gcr.io/kube-apiserver:v1.13.1docker tag mirrorgooglecontainers/kube-controller-manager:v1.13.1 k8s.gcr.io/kube-controller-manager:v1.13.1docker tag mirrorgooglecontainers/kube-scheduler:v1.13.1 k8s.gcr.io/kube-scheduler:v1.13.1docker tag mirrorgooglecontainers/kube-proxy:v1.13.1 k8s.gcr.io/kube-proxy:v1.13.1docker tag mirrorgooglecontainers/pause:3.1 k8s.gcr.io/pause:3.1docker tag mirrorgooglecontainers/etcd:3.2.24 k8s.gcr.io/etcd:3.2.24docker tag coredns/coredns:1.2.6 k8s.gcr.io/coredns:1.2.6docker tag registry.cn-shenzhen.aliyuncs.com/cp_m/flannel:v0.10.0-amd64 quay.io/coreos/flannel:v0.10.0-amd64docker rmi mirrorgooglecontainers/kube-apiserver:v1.13.1 docker rmi mirrorgooglecontainers/kube-controller-manager:v1.13.1 docker rmi mirrorgooglecontainers/kube-scheduler:v1.13.1 docker rmi mirrorgooglecontainers/kube-proxy:v1.13.1 docker rmi mirrorgooglecontainers/pause:3.1 docker rmi mirrorgooglecontainers/etcd:3.2.24 docker rmi coredns/coredns:1.2.6docker rmi registry.cn-shenzhen.aliyuncs.com/cp_m/flannel:v0.10.0-amd64然后再在 Master节点上执行如下命令初始化 k8s集群:kubeadm init –kubernetes-version=v1.13.1 –apiserver-advertise-address 192.168.39.79 –pod-network-cidr=10.244.0.0/16–kubernetes-version: 用于指定 k8s版本–apiserver-advertise-address:用于指定使用 Master的哪个network interface进行通信,若不指定,则 kubeadm会自动选择具有默认网关的 interface–pod-network-cidr:用于指定Pod的网络范围。该参数使用依赖于使用的网络方案,本文将使用经典的flannel网络方案。执行命令后,控制台给出了如下所示的详细集群初始化过程:[root@localhost ~]# kubeadm init –config kubeadm-config.yamlW1224 11:01:25.408209 10137 strict.go:54] error unmarshaling configuration schema.GroupVersionKind{Group:“kubeadm.k8s.io”, Version:“v1beta1”, Kind:“ClusterConfiguration”}: error unmarshaling JSON: while decoding JSON: json: unknown field “\u00a0 podSubnet”[init] Using Kubernetes version: v1.13.1[preflight] Running pre-flight checks[preflight] Pulling images required for setting up a Kubernetes cluster[preflight] This might take a minute or two, depending on the speed of your internet connection[preflight] You can also perform this action in beforehand using ‘kubeadm config images pull’[kubelet-start] Writing kubelet environment file with flags to file “/var/lib/kubelet/kubeadm-flags.env”[kubelet-start] Writing kubelet configuration to file “/var/lib/kubelet/config.yaml”[kubelet-start] Activating the kubelet service[certs] Using certificateDir folder “/etc/kubernetes/pki”[certs] Generating “etcd/ca” certificate and key[certs] Generating “etcd/healthcheck-client” certificate and key[certs] Generating “etcd/server” certificate and key[certs] etcd/server serving cert is signed for DNS names [localhost.localdomain localhost] and IPs [192.168.39.79 127.0.0.1 ::1][certs] Generating “etcd/peer” certificate and key[certs] etcd/peer serving cert is signed for DNS names [localhost.localdomain localhost] and IPs [192.168.39.79 127.0.0.1 ::1][certs] Generating “apiserver-etcd-client” certificate and key[certs] Generating “ca” certificate and key[certs] Generating “apiserver-kubelet-client” certificate and key[certs] Generating “apiserver” certificate and key[certs] apiserver serving cert is signed for DNS names [localhost.localdomain kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 192.168.39.79][certs] Generating “front-proxy-ca” certificate and key[certs] Generating “front-proxy-client” certificate and key[certs] Generating “sa” key and public key[kubeconfig] Using kubeconfig folder “/etc/kubernetes”[kubeconfig] Writing “admin.conf” kubeconfig file[kubeconfig] Writing “kubelet.conf” kubeconfig file[kubeconfig] Writing “controller-manager.conf” kubeconfig file[kubeconfig] Writing “scheduler.conf” kubeconfig file[control-plane] Using manifest folder “/etc/kubernetes/manifests”[control-plane] Creating static Pod manifest for “kube-apiserver”[control-plane] Creating static Pod manifest for “kube-controller-manager”[control-plane] Creating static Pod manifest for “kube-scheduler”[etcd] Creating static Pod manifest for local etcd in “/etc/kubernetes/manifests”[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory “/etc/kubernetes/manifests”. This can take up to 4m0s[apiclient] All control plane components are healthy after 24.005638 seconds[uploadconfig] storing the configuration used in ConfigMap “kubeadm-config” in the “kube-system” Namespace[kubelet] Creating a ConfigMap “kubelet-config-1.13” in namespace kube-system with the configuration for the kubelets in the cluster[patchnode] Uploading the CRI Socket information “/var/run/dockershim.sock” to the Node API object “localhost.localdomain” as an annotation[mark-control-plane] Marking the node localhost.localdomain as control-plane by adding the label “node-role.kubernetes.io/master=’’”[mark-control-plane] Marking the node localhost.localdomain as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule][bootstrap-token] Using token: 26uprk.t7vpbwxojest0tvq[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles[bootstraptoken] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials[bootstraptoken] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token[bootstraptoken] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster[bootstraptoken] creating the “cluster-info” ConfigMap in the “kube-public” namespace[addons] Applied essential addon: CoreDNS[addons] Applied essential addon: kube-proxyYour Kubernetes master has initialized successfully!To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/configYou should now deploy a pod network to the cluster.Run “kubectl apply -f [podnetwork].yaml” with one of the options listed at: https://kubernetes.io/docs/concepts/cluster-administration/addons/You can now join any number of machines by running the following on each nodeas root: kubeadm join 192.168.39.79:6443 –token 26uprk.t7vpbwxojest0tvq –discovery-token-ca-cert-hash sha256:028727c0c21f22dd29d119b080dcbebb37f5545e7da1968800140ffe225b0123[root@localhost ~]#0x02. 配置 kubectl在 Master上用 root用户执行下列命令来配置 kubectl:echo “export KUBECONFIG=/etc/kubernetes/admin.conf” >> /etc/profilesource /etc/profile echo $KUBECONFIG0x03. 安装Pod网络安装 Pod网络是 Pod之间进行通信的必要条件,k8s支持众多网络方案,这里我们依然选用经典的 flannel方案首先设置系统参数:sysctl net.bridge.bridge-nf-call-iptables=1然后在 Master节点上执行如下命令:kubectl apply -f kube-flannel.yamlkube-flannel.yaml 文件在此一旦 Pod网络安装完成,可以执行如下命令检查一下 CoreDNS Pod此刻是否正常运行起来了,一旦其正常运行起来,则可以继续后续步骤kubectl get pods –all-namespaces -o wide同时我们可以看到主节点已经就绪:kubectl get nodes添加 Slave节点在两个 Slave节点上分别执行如下命令来让其加入Master上已经就绪了的 k8s集群:kubeadm join –token <token> <master-ip>:<master-port> –discovery-token-ca-cert-hash sha256:<hash>如果 token忘记,则可以去 Master上执行如下命令来获取:kubeadm token list上述kubectl join命令的执行结果如下:[root@localhost ~]# kubeadm join 192.168.39.79:6443 –token yndddp.oamgloerxuune80q –discovery-token-ca-cert-hash sha256:7a45c40b5302aba7d8b9cbd3afc6d25c6bb8536dd6317aebcd2909b0427677c8[preflight] Running pre-flight checks[discovery] Trying to connect to API Server “192.168.39.79:6443”[discovery] Created cluster-info discovery client, requesting info from “https://192.168.39.79:6443”[discovery] Requesting info from “https://192.168.39.79:6443” again to validate TLS against the pinned public key[discovery] Cluster info signature and contents are valid and TLS certificate validates against pinned roots, will use API Server “192.168.39.79:6443”[discovery] Successfully established connection with API Server “192.168.39.79:6443”[join] Reading configuration from the cluster…[join] FYI: You can look at this config file with ‘kubectl -n kube-system get cm kubeadm-config -oyaml’[kubelet] Downloading configuration for the kubelet from the “kubelet-config-1.13” ConfigMap in the kube-system namespace[kubelet-start] Writing kubelet configuration to file “/var/lib/kubelet/config.yaml”[kubelet-start] Writing kubelet environment file with flags to file “/var/lib/kubelet/kubeadm-flags.env”[kubelet-start] Activating the kubelet service[tlsbootstrap] Waiting for the kubelet to perform the TLS Bootstrap…[patchnode] Uploading the CRI Socket information “/var/run/dockershim.sock” to the Node API object “localhost.localdomain” as an annotationThis node has joined the cluster:* Certificate signing request was sent to apiserver and a response was received.* The Kubelet was informed of the new secure connection details.Run ‘kubectl get nodes’ on the master to see this node join the cluster.效果验证查看节点状态kubectl get nodes查看所有 Pod状态kubectl get pods –all-namespaces -o wide好了,集群现在已经正常运行了,接下来看看如何正常的拆卸集群。拆卸集群首先处理各节点:kubectl drain <node name> –delete-local-data –force –ignore-daemonsetskubectl delete node <node name>一旦节点移除之后,则可以执行如下命令来重置集群:kubeadm reset安装 dashboard就像给elasticsearch配一个可视化的管理工具一样,我们最好也给 k8s集群配一个可视化的管理工具,便于管理集群。因此我们接下来安装 v1.10.0版本的 kubernetes-dashboard,用于集群可视化的管理。首先手动下载镜像并重新打标签:(所有节点)docker pull registry.cn-qingdao.aliyuncs.com/wangxiaoke/kubernetes-dashboard-amd64:v1.10.0docker tag registry.cn-qingdao.aliyuncs.com/wangxiaoke/kubernetes-dashboard-amd64:v1.10.0 k8s.gcr.io/kubernetes-dashboard-amd64:v1.10.0docker image rm registry.cn-qingdao.aliyuncs.com/wangxiaoke/kubernetes-dashboard-amd64:v1.10.0安装 dashboard:kubectl create -f dashboard.yamldashboard.yaml 文件在此查看 dashboard的 pod是否正常启动,如果正常说明安装成功: kubectl get pods –namespace=kube-system[root@k8s-master ~]# kubectl get pods –namespace=kube-systemNAME READY STATUS RESTARTS AGEcoredns-86c58d9df4-4rds2 1/1 Running 0 81mcoredns-86c58d9df4-rhtgq 1/1 Running 0 81metcd-k8s-master 1/1 Running 0 80mkube-apiserver-k8s-master 1/1 Running 0 80mkube-controller-manager-k8s-master 1/1 Running 0 80mkube-flannel-ds-amd64-8qzpx 1/1 Running 0 78mkube-flannel-ds-amd64-jvp59 1/1 Running 0 77mkube-flannel-ds-amd64-wztbk 1/1 Running 0 78mkube-proxy-crr7k 1/1 Running 0 81mkube-proxy-gk5vf 1/1 Running 0 78mkube-proxy-ktr27 1/1 Running 0 77mkube-scheduler-k8s-master 1/1 Running 0 80mkubernetes-dashboard-79ff88449c-v2jnc 1/1 Running 0 21s查看 dashboard的外网暴露端口kubectl get service –namespace=kube-systemNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEkube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP 5h38mkubernetes-dashboard NodePort 10.99.242.186 <none> 443:31234/TCP 14生成私钥和证书签名:openssl genrsa -des3 -passout pass:x -out dashboard.pass.key 2048openssl rsa -passin pass:x -in dashboard.pass.key -out dashboard.keyrm dashboard.pass.keyopenssl req -new -key dashboard.key -out dashboard.csr【如遇输入,一路回车即可】生成SSL证书:openssl x509 -req -sha256 -days 365 -in dashboard.csr -signkey dashboard.key -out dashboard.crt然后将生成的 dashboard.key 和 dashboard.crt置于路径 /home/share/certs下,该路径会配置到下面即将要操作的dashboard-user-role.yaml文件中创建 dashboard用户 kubectl create -f dashboard-user-role.yamldashboard-user-role.yaml 文件在此获取登陆tokenkubectl describe secret/$(kubectl get secret -nkube-system |grep admin|awk ‘{print $1}’) -nkube-system[root@k8s-master ~]# kubectl describe secret/$(kubectl get secret -nkube-system |grep admin|awk ‘{print $1}’) -nkube-systemName: admin-token-9d4vlNamespace: kube-systemLabels: <none>Annotations: kubernetes.io/service-account.name: admin kubernetes.io/service-account.uid: a320b00f-07ed-11e9-93f2-000c2978f207Type: kubernetes.io/service-account-tokenData====ca.crt: 1025 bytesnamespace: 11 bytestoken: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi10b2tlbi05ZDR2bCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJhZG1pbiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImEzMjBiMDBmLTA3ZWQtMTFlOS05M2YyLTAwMGMyOTc4ZjIwNyIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlLXN5c3RlbTphZG1pbiJ9.WbaHx-BfZEd0SvJwA9V_vGUe8jPMUHjKlkT7MWJ4JcQldRFY8Tdpv5GKCY25JsvT_GM3ob303r0yE6vjQdKna7EfQNO_Wb2j1Yu5UvZnWw52HhNudHNOVL_fFRKxkSVjAILA_C_HvW6aw6TG5h7zHARgl71I0LpW1VESeHeThipQ-pkt-Dr1jWcpPgE39cwxSgi-5qY4ssbyYBc2aPYLsqJibmE-KUhwmyOheF4Lxpg7E3SQEczsig2HjXpNtJizCu0kPyiR4qbbsusulH-kdgjhmD9_XWP9k0BzgutXWteV8Iqe4-uuRGHZAxgutCvaL5qENv4OAlaArlZqSgkNWwtoken既然生成成功,接下来就可以打开浏览器,输入 token来登录进集群管理页面:后 记由于能力有限,若有错误或者不当之处,还请大家批评指正,一起学习交流!My Personal Blog:CodeSheep 程序羊我的半年技术博客之路 ...

December 27, 2018 · 6 min · jiezi

pycharm+docker的配置(含jupyter notebook的镜像run方法)

背景搜了许多,都是较为陈旧的,目前docker的mac桌面版支持很快速的设置,不用找任何帮助前提假设你是想使用docker镜像,来避免python环境的复杂,节约时间的,来吧因为我最近开始研究ai,所以技术背景是aidocker pull ufoym/deepo:all-py36-jupyter-cpu4个多G申请pycharm专业版0.0.0.0 account.jetbrains.com ,添加到hostspycharm,嗯,Activate codeG91XMO9AVI-eyJsaWNlbnNlSWQiOiJHOTFYTU85QVZJIiwibGljZW5zZWVOYW1lIjoic29uZyB3YW5nIiwiYXNzaWduZWVOYW1lIjoiIiwiYXNzaWduZWVFbWFpbCI6IiIsImxpY2Vuc2VSZXN0cmljdGlvbiI6IkZvciBlZHVjYXRpb25hbCB1c2Ugb25seSIsImNoZWNrQ29uY3VycmVudFVzZSI6ZmFsc2UsInByb2R1Y3RzIjpbeyJjb2RlIjoiSUkiLCJwYWlkVXBUbyI6IjIwMTktMDMtMDYifSx7ImNvZGUiOiJSUzAiLCJwYWlkVXBUbyI6IjIwMTktMDMtMDYifSx7ImNvZGUiOiJXUyIsInBhaWRVcFRvIjoiMjAxOS0wMy0wNiJ9LHsiY29kZSI6IlJEIiwicGFpZFVwVG8iOiIyMDE5LTAzLTA2In0seyJjb2RlIjoiUkMiLCJwYWlkVXBUbyI6IjIwMTktMDMtMDYifSx7ImNvZGUiOiJEQyIsInBhaWRVcFRvIjoiMjAxOS0wMy0wNiJ9LHsiY29kZSI6IkRCIiwicGFpZFVwVG8iOiIyMDE5LTAzLTA2In0seyJjb2RlIjoiUk0iLCJwYWlkVXBUbyI6IjIwMTktMDMtMDYifSx7ImNvZGUiOiJETSIsInBhaWRVcFRvIjoiMjAxOS0wMy0wNiJ9LHsiY29kZSI6IkFDIiwicGFpZFVwVG8iOiIyMDE5LTAzLTA2In0seyJjb2RlIjoiRFBOIiwicGFpZFVwVG8iOiIyMDE5LTAzLTA2In0seyJjb2RlIjoiR08iLCJwYWlkVXBUbyI6IjIwMTktMDMtMDYifSx7ImNvZGUiOiJQUyIsInBhaWRVcFRvIjoiMjAxOS0wMy0wNiJ9LHsiY29kZSI6IkNMIiwicGFpZFVwVG8iOiIyMDE5LTAzLTA2In0seyJjb2RlIjoiUEMiLCJwYWlkVXBUbyI6IjIwMTktMDMtMDYifSx7ImNvZGUiOiJSU1UiLCJwYWlkVXBUbyI6IjIwMTktMDMtMDYifV0sImhhc2giOiI4MzE3MzUwLzAiLCJncmFjZVBlcmlvZERheXMiOjAsImF1dG9Qcm9sb25nYXRlZCI6ZmFsc2UsImlzQXV0b1Byb2xvbmdhdGVkIjpmYWxzZX0=-ko0/VqmEDlI6vfJX3gznVcqyf+mSlcQmXyavBbvCGT2ygKux7rS0kVhHA/2Skf7lMtHFIA3rvBwXRka/sy5LEJZocQtBsxT6dE+SOX34dxsUQpHsfrkc7ZwqD3Mg2we8mYl//TwGjtvAaXpxrC3z6qzxMvRRxdEjHsU5fTrdI1ondX/MOSKrSUAocf4Dx7Cpy+MBxL0xAcLOU5GP4wB8GJn/aPoxYqQxqXQc8BWFjUhZGnm21bc+5/WYqd08PH1QXTQLrly2Snt9yFboPfiGESx6fyVYcfA269syufzklfC1sCcViJjCna5V55rheSjDEjKOIRpRR/mKtOo8VdqVOA==-MIIEPjCCAiagAwIBAgIBBTANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1KZXRQcm9maWxlIENBMB4XDTE1MTEwMjA4MjE0OFoXDTE4MTEwMTA4MjE0OFowETEPMA0GA1UEAwwGcHJvZDN5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxcQkq+zdxlR2mmRYBPzGbUNdMN6OaXiXzxIWtMEkrJMO/5oUfQJbLLuMSMK0QHFmaI37WShyxZcfRCidwXjot4zmNBKnlyHodDij/78TmVqFl8nOeD5+07B8VEaIu7c3E1N+e1doC6wht4I4+IEmtsPAdoaj5WCQVQbrI8KeT8M9VcBIWX7fD0fhexfg3ZRt0xqwMcXGNp3DdJHiO0rCdU+Itv7EmtnSVq9jBG1usMSFvMowR25mju2JcPFp1+I4ZI+FqgR8gyG8oiNDyNEoAbsR3lOpI7grUYSvkB/xVy/VoklPCK2h0f0GJxFjnye8NT1PAywoyl7RmiAVRE/EKwIDAQABo4GZMIGWMAkGA1UdEwQCMAAwHQYDVR0OBBYEFGEpG9oZGcfLMGNBkY7SgHiMGgTcMEgGA1UdIwRBMD+AFKOetkhnQhI2Qb1t4Lm0oFKLl/GzoRykGjAYMRYwFAYDVQQDDA1KZXRQcm9maWxlIENBggkA0myxg7KDeeEwEwYDVR0lBAwwCgYIKwYBBQUHAwEwCwYDVR0PBAQDAgWgMA0GCSqGSIb3DQEBCwUAA4ICAQC9WZuYgQedSuOc5TOUSrRigMw4/+wuC5EtZBfvdl4HT/8vzMW/oUlIP4YCvA0XKyBaCJ2iX+ZCDKoPfiYXiaSiH+HxAPV6J79vvouxKrWg2XV6ShFtPLP+0gPdGq3x9R3+kJbmAm8w+FOdlWqAfJrLvpzMGNeDU14YGXiZ9bVzmIQbwrBA+c/F4tlK/DV07dsNExihqFoibnqDiVNTGombaU2dDup2gwKdL81ua8EIcGNExHe82kjF4zwfadHk3bQVvbfdAwxcDy4xBjs3L4raPLU3yenSzr/OEur1+jfOxnQSmEcMXKXgrAQ9U55gwjcOFKrgOxEdek/Sk1VfOjvS+nuM4eyEruFMfaZHzoQiuw4IqgGc45ohFH0UUyjYcuFxxDSU9lMCv8qdHKm+wnPRb0l9l5vXsCBDuhAGYD6ss+Ga+aDY6f/qXZuUCEUOH3QUNbbCUlviSz6+GiRnt1kA9N2Qachl+2yBfaqUqr8h7Z2gsx5LcIf5kYNsqJ0GavXTVyWh7PYiKX4bs354ZQLUwwa/cG++2+wNWP+HtBhVxMRNTdVhSm38AknZlD+PTAsWGu9GyLmhti2EnVwGybSD2Dxmhxk3IPCkhKAK+pl0eWYGZWG3tJ9mZ7SowcXLWDFAk0lRJnKGFMTggrWjV8GYpw5bq23VmIqqDLgkNzuoog==设置专业版只有专业版有docker的remote调试,ce版本没有只需要进入project进行设置,如下图可以采用另外一种方法:社区版在本地做实验,和学习,足够了docker镜像采用有jupyterbook的images,全搞定docker pull ufoym/deepo:all-py36-jupyter-cpu或者折扣申请https://www.jetbrains.com/pyc…也可以采用的最简便的:jupyter book进入pycharm,引用numpy,如图,remote包ok啦jupyter notebook(使用docker)ZBMAC-C02N753CG:test jiaohuifeng$ ./jupyter.sh [I 08:26:23.100 NotebookApp] Writing notebook server cookie secret to /root/.local/share/jupyter/runtime/notebook_cookie_secret[W 08:26:23.537 NotebookApp] All authentication is disabled. Anyone who can connect to this server will be able to run code.[I 08:26:23.552 NotebookApp] Serving notebooks from local directory: /root[I 08:26:23.552 NotebookApp] 0 active kernels[I 08:26:23.552 NotebookApp] The Jupyter Notebook is running at:[I 08:26:23.553 NotebookApp] http://0.0.0.0:8888/[I 08:26:23.553 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).jupyter.sh docker run -it -v /Users/jiaohuifeng/test:/sdata -p 8888:8888 –ipc=host ufoym/deepo:all-py36-jupyter-cpu jupyter notebook –no-browser –ip=0.0.0.0 –allow-root –NotebookApp.token= –notebook-dir=’/root' ...

December 25, 2018 · 1 min · jiezi

cnn卷积神经网络打造人脸登录系统

git地址:https://github.com/chenlinzho…本文主要介绍了系统涉及的人脸检测与识别的详细方法,该系统基于python2.7.10/opencv2/tensorflow1.7.0环境,实现了从摄像头读取视频,检测人脸,识别人脸的功能由于模型文件过大,git无法上传,整个项目放在百度云盘,地址:https://pan.baidu.com/s/1Taal…人脸识别是计算机视觉研究领域的一个热点。目前,在实验室环境下,许多人脸识别已经赶上(超过)人工识别精度(准确率:0.9427~0.9920),比如face++,DeepID3,FaceNet等(详情可以参考:基于深度学习的人脸识别技术综述)。但是,由于光线,角度,表情,年龄等多种因素,导致人脸识别技术无法在现实生活中广泛应用。本文基于python/opencv/tensorflow环境,采用FaceNet(LFW:0.9963 )为基础来构建实时人脸检测与识别系统,探索人脸识别系统在现实应用中的难点。下文主要内容如下 :利用htm5 video标签打开摄像头采集头像并使用jquery.faceDeaction组件来粗略检测人脸将人脸图像上传到服务器,采用mtcnn检测人脸利用opencv的仿射变换对人脸进行对齐,保存对齐后的人脸采用预训练的facenet对检测的人脸进行embedding,embedding成512维度的特征;对人脸embedding特征创建高效的annoy索引进行人脸检测人脸采集采用html5 video标签可以很方便的实现从摄像头读取视频帧,下文代码实现了从摄像头读取视频帧,faceDection识别人脸后截取图像上传到服务器功能在html文件中添加video,canvas标签<div class=“booth”> <video id=“video” width=“400” height=“300” muted class=“abs” ></video> <canvas id=“canvas” width=“400” height=“300”></canvas> </div>打开网络摄像头var video = document.getElementById(‘video’),var vendorUrl = window.URL || window.webkitURL;//媒体对象navigator.getMedia = navigator.getUserMedia || navagator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;navigator.getMedia({video: true, //使用摄像头对象audio: false //不适用音频}, function(strem){ video.src = vendorUrl.createObjectURL(strem); video.play();});利用jquery的facetDection组件检测人脸$(’#canvas’).faceDetection()检测出人连脸的话截图,并把图片转换为base64的格式,方便上传context.drawImage(video, 0, 0, video.width, video.height);var base64 = canvas.toDataURL(‘images/png’);将base64格式的图片上传到服务器//上传人脸图片function upload(base64) { $.ajax({ “type”:“POST”, “url”:"/upload.php", “data”:{‘img’:base64}, ‘dataType’:‘json’, beforeSend:function(){}, success:function(result){ console.log(result) img_path = result.data.file_path } });}图片服务器接受代码,php语言实现function base64_image_content($base64_image_content,$path){ //匹配出图片的格式 if (preg_match(’/^(data:\s*image/(\w+);base64,)/’, $base64_image_content, $result)){ $type = $result[2]; $new_file = $path."/"; if(!file_exists($new_file)){ //检查是否有该文件夹,如果没有就创建,并给予最高权限 mkdir($new_file, 0700,true); } $new_file = $new_file.time().".{$type}"; if (file_put_contents($new_file, base64_decode(str_replace($result[1], ‘’, $base64_image_content)))){ return $new_file; }else{ return false; } }else{ return false; }}人脸检测人脸检测方法有许多,比如opencv自带的人脸Haar特征分类器和dlib人脸检测方法等。对于opencv的人脸检测方法,有点是简单,快速;存在的问题是人脸检测效果不好。正面/垂直/光线较好的人脸,该方法可以检测出来,而侧面/歪斜/光线不好的人脸,无法检测。因此,该方法不适合现场应用。对于dlib人脸检测方法 ,效果好于opencv的方法,但是检测力度也难以达到现场应用标准。本文中,我们采用了基于深度学习方法的mtcnn人脸检测系统(mtcnn:Joint Face Detection and Alignment using Multi-task Cascaded Convolutional Neural Networks)。mtcnn人脸检测方法对自然环境中光线,角度和人脸表情变化更具有鲁棒性,人脸检测效果更好;同时,内存消耗不大,可以实现实时人脸检测。本文中采用mtcnn是基于python和tensorflow的实现(代码来自于davidsandberg,caffe实现代码参见:kpzhang93)model= os.path.abspath(face_comm.get_conf(‘mtcnn’,‘model’))class Detect: def init(self): self.detector = MtcnnDetector(model_folder=model, ctx=mx.cpu(0), num_worker=4, accurate_landmark=False) def detect_face(self,image): img = cv2.imread(image) results =self.detector.detect_face(img) boxes=[] key_points = [] if results is not None: #box框 boxes=results[0] #人脸5个关键点 points = results[1] for i in results[0]: faceKeyPoint = [] for p in points: for i in range(5): faceKeyPoint.append([p[i], p[i + 5]]) key_points.append(faceKeyPoint) return {“boxes”:boxes,“face_key_point”:key_points}具体代码参考fcce_detect.py人脸对齐有时候我们截取的人脸了头像可能是歪的,为了提升检测的质量,需要把人脸校正到同一个标准位置,这个位置是我们定义的,假设我们设定的标准检测头像是这样的假设眼睛,鼻子三个点的坐标分别是a(10,30) b(20,30) c(15,45),具体设置可参看config.ini文件alignment块配置项采用opencv仿射变换进行对齐,获取仿射变换矩阵dst_point=【a,b,c】tranform = cv2.getAffineTransform(source_point, dst_point)仿射变换:img_new = cv2.warpAffine(img, tranform, imagesize)具体代码参考face_alignment.py文件产生特征对齐得到后的头像,放入采用预训练的facenet对检测的人脸进行embedding,embedding成512维度的特征,以(id,vector)的形式保存在lmdb文件中 facenet.load_model(facenet_model_checkpoint) images_placeholder = tf.get_default_graph().get_tensor_by_name(“input:0”) embeddings = tf.get_default_graph().get_tensor_by_name(“embeddings:0”) phase_train_placeholder = tf.get_default_graph().get_tensor_by_name(“phase_train:0”) face=self.dectection.find_faces(image) prewhiten_face = facenet.prewhiten(face.image) # Run forward pass to calculate embeddings feed_dict = {images_placeholder: [prewhiten_face], phase_train_placeholder: False} return self.sess.run(embeddings, feed_dict=feed_dict)[0]具体代码可参看face_encoder.py人脸特征索引:人脸识别的时候不能对每一个人脸都进行比较,太慢了,相同的人得到的特征索引都是比较类似,可以采用KNN分类算法去识别,这里采用是更高效annoy算法对人脸特征创建索引,annoy索引算法的有个假设就是,每个人脸特征可以看做是在高维空间的一个点,如果两个很接近(相识),任何超平面都无法把他们分开,也就是说如果空间的点很接近,用超平面去分隔,相似的点一定会分在同一个平面空间(具体参看:https://github.com/spotify/annoy)#人脸特征先存储在lmdb文件中格式(id,vector),所以这里从lmdb文件中加载lmdb_file = self.lmdb_fileif os.path.isdir(lmdb_file): evn = lmdb.open(lmdb_file) wfp = evn.begin() annoy = AnnoyIndex(self.f) for key, value in wfp.cursor(): key = int(key) value = face_comm.str_to_embed(value) annoy.add_item(key,value) annoy.build(self.num_trees) annoy.save(self.annoy_index_path)具体代码可参看face_annoy.py人脸识别经过上面三个步骤后,得到人脸特征,在索引中查询最近几个点并就按欧式距离,如果距离小于0.6(更据实际情况设置的阈值)则认为是同一个人,然后根据id在数据库查找到对应人的信息即可#根据人脸特征找到相似的def query_vector(self,face_vector): n=int(face_comm.get_conf(‘annoy’,’num_nn_nearst’)) return self.annoy.get_nns_by_vector(face_vector,n,include_distances=True)具体代码可参看face_annoy.py安装部署系统采用有两个模块组成:face_web:提供用户注册登录,人脸采集,php语言实现face_server: 提供人脸检测,裁剪,对齐,识别功能,python语言实现模块间采用socket方式通信通信格式为: length+contentface_server相关的配置在config.ini文件中1.使用镜像face_serverdocker镜像: shareclz/python2.7.10-face-imageface_web镜像: skiychan/nginx-php7假设项目路径为/data1/face-login2.安装face_server容器docker run -it –name=face_server –net=host -v /data1:/data1 shareclz/python2.7.10-face-image /bin/bashcd /data1/face-loginpython face_server.py3.安装face_web容器docker run -it –name=face_web –net=host -v /data1:/data1 skiychan/nginx-php7 /bin/bashcd /data1/face-login;php -S 0.0.0.0:9988 -t ./web/ 最终效果:face_server加载mtcnn模型和facenet模型后等待人脸请求未注册识别失败人脸注册注册后登录成功参考https://zhuanlan.zhihu.com/p/…https://github.com/spotify/annoyhttps://blog.csdn.net/just_so...https://blog.csdn.net/oTengYu… ...

December 24, 2018 · 2 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学习

kuberneteskubernetes 知识点1. 核心组件etcd 保存整个集群的状态信息,感觉相当于k8s的数据库apiserver 提供对k8s资源操作的唯一入口,并提供认证授权,访问控制,API注册与发现等机制controller manager 负责维护集群的状态,eg:故障检测,自动扩展pod,滚动更新等scheduler 负责对资源的调度,按着预定的调度策略将pod调度到相应的集群上kubelet 负责维护容器的生命周期,相当于在node上的agent,负责管理pods和它们上面的容器,images镜像、volumes等kube-proxy 负责为service提供集群内部的服务发现和负载均衡2. kubernetes 常用命令查看集群信息kubectl cluster-info在集群中运行一个应用程序kubectl run nginx-test –replicas=3 –labels=‘app=nginx’ –image=nginx:latest –port=80 #使用kubectl run命令启动一个pod,自定义名称为nginx-test,启动了3个pod副本,并给pod打上标签app=nginx,这个pod拉取docker镜像nginx:latest,开放端口80查看集群中所有podkubectl get pokubectl get podkubectl get pods根据标签label查看集群中podkubectl get pods -l appkubectl get pods -l app=nginx查看标签为app=nginx的pod在集群中具体分配在哪个节点和pod的ipkubectl get pods -l app=nginx -o wide查看pod的详细信息kubectl describe pods <podname>查看集群中的deployment(其他命令与pod类似)kubectl get deploy查看集群中的replica set(其他命令与pod类似)kubectl get replicasetkubectl get rs创建一个service,集群中的资源通过service与外界交互kubectl expose deploy nginx-test –port=8080 –target-port=80 –name=nginx-service#k8s集群通过deploy来管理,导出名为nginx-test的deploy,为其创建名为nginx-service的服务开放给外界,使外界能通过nginx-service来和nginx-test交互,外部端口为8080,内部端口为80查看集群中的服务(其他命令与pod类似)kubectl get svc查看pod中容器的日志kubectl log <podname> #查看指定pod内容器的日志 kubectl log -l app=nginx #查看标签lable为app=nginx下的pod的容器日志pod的副本的扩容和缩容kubectl scale deploy nginx-test –replicas10#通过kubectl scale将名为nginx-test的deploy重新定义有10个副本pod查看pod副本扩容缩容的实时进度kubectl rollout status deploy nginx-test删除资源pod和rs不能直接被删除,其被deploy控制,即使删除了某一pod,也会创建新的pod来对应配置pod副本数量,要想删除pod,只能用删除其deploy来删除,或者变更pod副本配置缩容(如上)kubectl delete deploy nginx-test #删除部署的deploy(删除其对于的pod和rs)kubectl delete svc nginx-service #删除创建的service3. 应用创建部署yaml文件 tomsun28之后的k8s应用部署修改,都确定使用apply形式部署更新,使用git版本控制创建资源,好处多多kubectl apply -f nginx.yaml ##更新式创建资源,如果不存在此资源则创建,如存在改动则调整资源(推荐)kubectl delete -f nginx.yaml #资源(pod,deployment,service,replicaset…)删除销毁kubernetes部署nginx集群nginx.yaml :# ———————-nginx——————— ## ——nginx deployment—— #kind: DeploymentapiVersion: apps/v1beta2metadata: name: nginx-deployment labels: app: nginxspec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: 192.167.2.144:5000/nginx:latest ports: - containerPort: 80—# ——-nginx-service——— #apiVersion: v1kind: Servicemetadata: name: nginx-servicespec: type: NodePort ports: - port: 80 targetPort: 80 nodePort: 30001 selector: app: nginxkubectl apply -f nginx.yaml记一下对kubernetes集群的搭建部署ubantu下用kubeadm搭建kubernetes集群 官方安装教程ubuntu + docker 环境 (目前是两个服务器组建集群server1+server2)安装kubelet kubeadm和kubectl安装 apt-transport-https # apt-get update && apt-get install -y apt-transport-https 安装gpg证书(阿里镜像仓库的k8s) # curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add - 更新软件源信息 # cat << EOF >/etc/apt/sources.list.d/kubernetes.list deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main EOF 更新并安装kubelet kubeadm kubectl # apt-get update && apt-get install -y kubelet kubeadm kubectl 指定版本为: # apt-get update && apt-get install -y kubelet=1.11.1-00 kubeadm=1.11.1-00 kubectl=1.11.1-00 关闭swap sudo swapoff -a master server1上初始化部署kubernetes的master获取初始化所需版本docker镜像,k8s=v1.11.1在我的docker hub ’s tomsun28可以拉取# kubeadm config images list ##查询当前kubeadm版本所需images# kubeadm config images pull ##拉取这些imagesk8s=v1.11.1所对应镜像及版本:k8s.gcr.io/coredns:1.1.3k8s.gcr.io/etcd-amd64:3.2.18k8s.gcr.io/kube-apiserver-amd64:v1.11.1k8s.gcr.io/kube-controller-manager-amd64:v1.11.1k8s.gcr.io/kube-proxy-amd64:v1.11.1k8s.gcr.io/kube-scheduler-amd64:v1.11.1k8s.gcr.io/pause:3.1初始化master kubeadm init –kubernetes-version=v1.11.1 –apiserver-advertise-address=116.196.81.106 –pod-network-cidr=10.244.0.0/16 –apiserver-advertise-address=<ip>指定apiserver的访问ip,ip默认为当前虚拟机的默认网卡ip.当ip为内网地址时,k8s集群只能搭建在网段内部,如果有需求通过外网ip来操作apiserver,需要在启动集群时添加可信参数 –apiserver-cert-extra-sans=116.196.81.106 将外网的ip添加进去. 当ip为外网地址时,可以实现不同网段的虚拟机组成k8s集群(目前我就是这个需要,一个京东云一个阿里云),暂时还没测这种跨公网的集群性能咋样,毕竟考虑到网速带宽等不如内网,但有一个优势就是可以整合不同的资源,不被同一云商所束缚,jd挂了ali还可以用.成功之后会有join集群的脚步提示,记一下 kubeadm join 192.168.0.3:6443 –token q6gmgt.3dakenwttapw4n2o –discovery-token-ca-cert-hash sha256:dbf69119e962456c239c5f7821ee9a0db46fb643fc40da8776d4e032de072085 根据output提示,to start using your cluster, you need to run(no root user )mkdir -p $HOME/.kubesudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/configsudo chown $(id -u):$(id -g) $HOME/.kube/config或者(root user): export KUBECONFIG=/etc/kubernetes/admin.conf 安装 pod network 提供 pods 节点之前相互通信运行下面命令设置 /proc/sys/net/bridge/bridge-nf-call-iptables为1sysctl net.bridge.bridge-nf-call-iptables=1选择 flannel 作为 pod networkkubectl apply -f https://raw.githubusercontent.com/coreos/flannel/c5d10c8/Documentation/kube-flannel.yml要使 flannel 能正常使用,需要在master初始化时 kubeadm init 添加对应pod-network-cidr kubeadm init –pod-network-cidr=10.244.0.0/16 解除master不能调度运行其他pod的限制 kubectl taint nodes –all node-role.kubernetes.io/master- server2上部署kebernetes并作为节点join to master在server2服务器上执行步骤2作为node节点加入到master集群中 kubeadm join –token <token> <master-ip>:<master-port> –discovery-token-ca-cert-hash sha256:<hash> 在master上查看集群node节点分布 kubectl get nodes 对kubeadm所做的搭建进行undo revert kubeadm reset 参考来自kubernetes官方部署文档 转载请注明 from tomsun28 ...

December 23, 2018 · 2 min · jiezi

在Docker中使用Xdebug

首发于 樊浩柏科学院我们经常会使用 PhpStorm 结合 Xdebug 进行代码断点调试,这样能追踪程序执行流程,方便调试代码和发现潜在问题。博主将开发环境迁入 Docker 后,Xdebug 调试遇到了些问题,在这里整理出 Docker 中使用 Xdebug 的方法和注意事项。说明:开发和调试环境为本地 Docker 中的 LNMP,IDE 环境为本地 Win10 下的 PhpStorm。这种情况下 Xdebug 属于远程调试模式,IDE 和本地 IP 为 192.168.1.101,Docker 中 LNMP 容器 IP 为 172.17.0.2。问题描述在 Docker 中安装并配置完 Xdebug ,并设置 PhpStorm 中对应的 Debug 参数后,但是 Debug 并不能正常工作。此时,php.ini中 Xdebug 配置如下:xdebug.idekey = phpstormxdebug.remote_enable = onxdebug.remote_connect_back = onxdebug.remote_port = 9001 //PhpStorm监听本地9001端口xdebug.remote_handler = dbgpxdebug.remote_log = /home/tmp/xdebug.log开始收集问题详细表述。首先,观察到 PhpStorm 的 Debug 控制台出现状态:Waiting for incoming connection with ide key ***然后查看 Xdebug 调试日志xdebug.log,存在如下错误:I: Checking remote connect back address.I: Checking header ‘HTTP_X_FORWARDED_FOR’.I: Checking header ‘REMOTE_ADDR’.I: Remote address found, connecting to 172.17.0.1:9001.W: Creating socket for ‘172.17.0.1:9001’, poll success, but error: Operation now in progress (29).E: Could not connect to client. :-(分析问题查看这些问题表述,基本上可以定位为 Xdebug 和 PhpStorm 之间的 网络通信 问题,接下来一步步定位具体问题。排查本地9001端口Win 下执行 netstat -ant命令:协议 本地地址 外部地址 状态 卸载状态TCP 0.0.0.0:9001 0.0.0.0:0 LISTENING InHost端口 9001 监听正常,然后在容器中使用 telnet 尝试同本地 9001 端口建立 TCP 连接:$ telnet 192.168.1.101 9001Trying 192.168.1.101…Connected to 192.168.1.101.Escape character is ‘^]’.说明容器同本地 9001 建立 TCP 连接正常,但是 Xdebug 为什么会报连接失败呢?此时,至少可以排除不会是因为 PhpStorm 端配置的问题。排查Xdebug问题回过头来看看 Xdebug 的错误日志,注意观察到失败时的连接信息:I: Remote address found, connecting to 172.17.0.1:9001.W: Creating socket for ‘172.17.0.1:9001’, poll success, but error: Operation now in progress (29).E: Could not connect to client. :-(此时,在容器中使用 tcpdump 截获的数据包如下:$ tcpdump -nnA port 9001# 尝试建立连接,但是失败了12:20:34.318080 IP 172.17.0.2.40720 > 172.17.0.1.9001: Flags [S], seq 2365657644, win 29200, options [mss 1460,sackOK,TS val 833443 ecr 0,nop,wscale 7], length 0E..<..@.@.=………..#)…,……r.XT…………………12:20:34.318123 IP 172.17.0.1.9001 > 172.17.0.2.40720: Flags [R.], seq 0, ack 2365657645, win 0, length 0E..(.]@.@..M……..#)………-P….B..可以确定的是, Xdebug 是向 IP 为 172.17.0.1 且端口为 9001 的目标机器尝试建立 TCP 连接,而非正确的 192.168.1.101 本地 IP。到底发生了什么?首先,为了搞懂 Xdebug 和 PhpStorm 的交互过程,查了 官方手册 得知,Xdebug 工作在远程调试模式时,有两种工作方式:1、IDE 所在机器 IP 确定/单人开发图中,由于 IDE 的 IP 和监听端口都已知,所以 Xdebug 端可以很明确知道 DBGP 交互时 IDE 目标机器信息,所以 Xdebug 只需配置 xdebug.remote_host、xdebug.remote_port 即可。2、IDE 所在机器 IP 未知/团队开发由于 IDE 的 IP 未知或者 IDE 存在多个 ,那么 Xdebug 无法提前预知 DBGP 交互时的目标 IP,所以不能直接配置 xdebug.remote_host 项(remote_port 项可以确定),必须设置 xdebug.remote_connect_back 为 On 标识(会忽略 xdebug.remote_host 项)。这时,Xdebug 会优先获取 HTTP_X_FORWARDED_FOR 和 REMOTE_ADDR 中的一个值作为通信时 IDE 端的目标 IP,通过Xdebug.log记录可以确认。I: Checking remote connect back address.I: Checking header ‘HTTP_X_FORWARDED_FOR’.I: Checking header ‘REMOTE_ADDR’.I: Remote address found接下来,可以知道 Xdebug 端是工作在远程调试的模式 2 上,Xdebug 会通过 HTTP_X_FORWARDED_FOR 和 REMOTE_ADDR 项获取目标机 IP。Docker 启动容器时已经做了 80 端口映射,忽略宿主机同 Docker 容器复杂的数据包转发规则,先截取容器 80 端口数据包:$ tcpdump -nnA port 80# 请求信息13:30:07.017770 IP 172.17.0.1.33976 > 172.17.0.2.80: Flags [P.], seq 1:208, ack 1, win 229, options [nop,nop,TS val 1250713 ecr 1250713], length 207E….=@.@…………..P.. .+…….Y…………..GET /v2/room/list.json HTTP/1.1Accept: */*Cache-Control: no-cacheHost: localhostConnection: Keep-AliveUser-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_152-release)Accept-Encoding: gzip,deflate可以看出,数据包的源地址为 172.17.0.1,并非真正的源地址 192.168.1.101,HTTP 请求头中也无 HTTP_X_FORWARDED_FOR 项。说明:172.17.0.1 实际为 Docker 创建的虚拟网桥 docker0 的地址 ,也是所有容器的默认网关。Docker 网络通信方式默认为 Bridge 模式,通信时宿主机会对数据包进行 SNAT 转换,进而源地址变为 docker0,那么,怎么在 Docker 里获取客户端真正 IP 呢?。定位根源最后,可以确定由于 HTTP_X_FORWARDED_FOR 未定义,因此 Xdebug 会取 REMOTE_ADDR 为 IDE 的 IP,同时由于 Docker 特殊的网络转发规则,导致 REMOTE_ADDR 变更为网关 IP,所以 Xdebug 同 PhpStorm 进行 DBGP 交互会失败。解决问题由于 Docker 容器里获取真正客户端 IP 比较复杂,这里使用 Xdebug 的 远程模式 1 明确 IDE 端 IP 来规避源 IP 被修改的情况,最终解决 Xdebug 调试问题。模式 1 的 Xdebug 主要配置为://并没有xdebug.remote_connect_back项xdebug.idekey = phpstormxdebug.remote_enable = onxdebug.remote_host = 192.168.1.101xdebug.remote_port = 9001xdebug.remote_handler = dbgp重启 php-fpm,使用php –ri xdebug确定无误,使用 PhpStorm 重新进行调试。再次在容器中 tcpdump 抓取 9001 端口数据包:# 连接的源地址已经正确14:05:27.379783 IP 172.17.0.2.44668 > 192.168.1.101.9001: Flags [S], seq 3444466556, win 29200, options [mss 1460,sackOK,TS val 1462749 ecr 0,nop,wscale 7], length 0E..<2.@.@……….e.|#).Nc|……r.nO………..Q………再次使用 PhpStorm 的 REST Client 断点调试 API 时, Debug 控制台如下:所以,使用 Xdebug 进行远程调试时,需要选择合适的调试模式,在 Docker 下建议使用远程模式 1。其他注意事项Xdebug 版本和 PHP 版本一致并不是每个 Xdebug 版本都适配 PHP 每个版本,可以直接使用 官方工具,选择合适的 Xdebug 版本。本地文件和远端文件映射关系如上图,在使用 PhpStorm 时进行远程调试时,需要配置本地文件和远端文件的目录映射关系,这样 IDE 才能根据 Xdebug 传递的当前执行文件路径与本地文件做匹配,实现断点调试和单步调试等。 ...

December 22, 2018 · 3 min · jiezi

Kubernetes pod里一个特殊的容器:pause-amd64

大家在使用Docker容器或者Kubernetes时,遇到过这个容器么?gcr.io/google_containers/pause-amd64docker ps的命令返回的结果:[root@k8s-minion1 kubernetes]# docker ps |grep pausec3026adee957 gcr.io/google_containers/pause-amd64:3.0 “/pause” 22 minutes ago Up 22 minutes k8s_POD.d8dbe16c_redis-master-343230949-04glm_default_ce3f60a9-095d-11e7-914b-0a77ecd65f3e_66c108d5202df18d636e gcr.io/google_containers/pause-amd64:3.0 “/pause” 24 hours ago Up 24 hours k8s_POD.d8dbe16c_kube-proxy-js0z0_kube-system_2866cfc2-0891-11e7-914b-0a77ecd65f3e_c8e1a667072d3414d33a gcr.io/google_containers/pause-amd64:3.0 “/pause” 24 hours ago Up 24 hours k8s_POD.d8dbe16c_kube-flannel-ds-tsps5_default_2866e3fb-0891-11e7-914b-0a77ecd65f3e_be4b719e[root@k8s-minion1 kubernetes]#Kubernetes的官网解释:it’s part of the infrastructure. This container is started first in all Pods to setup the network for the Pod.意思是:pause-amd64是Kubernetes基础设施的一部分,Kubernetes管理的所有pod里,pause-amd64容器是第一个启动的,用于实现Kubernetes集群里pod之间的网络通讯。对这个特殊容器感兴趣的朋友,可以阅读其源代码:https://github.com/kubernetes…我们查看这个pause-amd64镜像的dockerfile,发现实现很简单,基于一个空白镜像开始:FROM scratchARG ARCHADD bin/pause-${ARCH} /pauseENTRYPOINT ["/pause"]ARG指令用于指定在执行docker build命令时传递进去的参数。这个pause container是用C语言写的:https://www.ianlewis.org/en/a…在运行的Kubernetes node上运行docker ps,能发现这些pause container:pause container作为pod里其他所有container的parent container,主要有两个职责:是pod里其他容器共享Linux namespace的基础扮演PID 1的角色,负责处理僵尸进程这两点我会逐一细说。在Linux里,当父进程fork一个新进程时,子进程会从父进程继承namespace。目前Linux实现了六种类型的namespace,每一个namespace是包装了一些全局系统资源的抽象集合,这一抽象集合使得在进程的命名空间中可以看到全局系统资源。命名空间的一个总体目标是支持轻量级虚拟化工具container的实现,container机制本身对外提供一组进程,这组进程自己会认为它们就是系统唯一存在的进程。在Linux里,父进程fork的子进程会继承父进程的命名空间。与这种行为相反的一个系统命令就是unshare:再来聊聊pause容器如何处理僵尸进程的。Pause容器内其实就运行了一个非常简单的进程,其逻辑可以从前面提到的Pause github仓库上找到:static void sigdown(int signo) { psignal(signo, “Shutting down, got signal”); exit(0);}static void sigreap(int signo) { while (waitpid(-1, NULL, WNOHANG) > 0);}int main() { if (getpid() != 1) /* Not an error because pause sees use outside of infra containers. */ fprintf(stderr, “Warning: pause should be the first process\n”); if (sigaction(SIGINT, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0) return 1; if (sigaction(SIGTERM, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0) return 2; if (sigaction(SIGCHLD, &(struct sigaction){.sa_handler = sigreap, .sa_flags = SA_NOCLDSTOP}, NULL) < 0) return 3; for (;;) pause(); fprintf(stderr, “Error: infinite loop terminated\n”); return 42;}这个c语言实现的进程,核心代码就28行:其中第24行里一个无限循环for(;;), 至此大家能看出来pause容器名称的由来了吧?这个无限循环里执行的是一个系统调用pause,因此pause容器大部分时间都在沉睡,等待有信号将其唤醒。接收什么信号呢?一旦收到SIGCHLD信号,pause进程就执行注册的sigreap函数。看下SIGCHLD信号的帮助:SIGCHLD,在一个进程正常终止或者停止时,将SIGCHLD信号发送给其父进程,按系统默认将忽略此信号,如果父进程希望被告知其子系统的这种状态,则应捕捉此信号。pause进程注册的信号处理函数sigreap里,调用另一个系统调用waitpid来获得子进程终止的原因。希望这篇文章对大家理解Kubernetes里的pause容器有所帮助。感谢阅读。要获取更多Jerry的原创文章,请关注公众号"汪子熙": ...

December 21, 2018 · 1 min · jiezi

容器和容器镜像的区别,您真的了解吗

很多刚刚接触容器技术的朋友,不容易弄清楚容器,容器镜像和Docker这几个词的区别和联系。我们首先来看容器和容器镜像。举个例子,执行命令行docker search nginx,搜索结果的一条条记录就是一个个容器镜像。所谓镜像,就是一个静态概念,一个镜像由若干只读层(read-only layer)构成。上图左边是Docker镜像的内部实现细节,我们能看到多个只读层叠加在一起,层与层之间通过指针关联,这些层能够在运行Docker的宿主机的文件系统上访问到。Linux的统一文件系统(union file system)技术将这些叠加的只读层合并成一个文件系统,该系统为这些只读层提供了一个统一的视角,从而为Docker的用户隐藏了多层的存在。从Docker用户的视角出发,一个Docker镜像只存在一个文件系统,即上图右边所示。这些文件系统的设计是Docker实现细节,一般情况下我们不用去深究。但如果您足够好奇,使用命令sudo tree浏览目录 /var/lib/docker即可:比如我用命令docker images浏览下载到本地的docker镜像:其中一个叫jerry-nginx的镜像,是一个web应用,它的所有内容能在/var/lib/docker目录下的这个目录查到:讲完了容器镜像,我们再来看容器。容器和容器镜像一样,也是若干层的叠加,唯一区别是所有只读层的最上面一层,是一层可读可写层,如上图绿色图例所示。初学者可以记住这个简单的公式:容器 = 容器镜像 + 可读可写层我们如果用命令docker ps –all查看本机所有容器列表,会发现有的容器处于运行状态,有的处于退出状态。因此,一个处于运行状态的容器(running container)包含一个可读写的文件系统加上隔离的进程空间。容器里的进程可以对这个可读写文件系统内的文件进行修改、删除、创建等操作。镜像里每一层其实都能在docker文件夹的containers子目录下找到:上图每一个红色文件夹代表镜像里的一层,蓝色文件包含了该层运行时的日志文件,或者网络相关配置等。做个实验:ubuntu这个容器执行结束后,使用find / -name i042416.txt文件,这说明docker运行时能对宿主机的文件系统进行写操作。下面分析几个常用的易混淆的命令。docker create <image-id>先看它的帮助文档:试着执行以下:产生一个输出id:7ee10851c3f1e53bbd35e5f196f34de560afa1a20d9bf1ced587630dbcda877bcreate创建的容器,状态变为created:docker create命令给通过命令行传入的容器镜像创建了一个新的可读可写层,从而生成了一个新的容器实例:然后再执行docker start,输入docker create创建的容器实例ID,就可以启动这个容器实例了。而docker run其实就是docker create和docker start这两个命令合二为一的版本。希望这篇文章能帮助大家理解容器和容器镜像的区别。要获取更多Jerry的原创文章,请关注公众号"汪子熙":

December 21, 2018 · 1 min · jiezi

Docker技术三大要点:cgroup, namespace和unionFS的理解

www.docker.com的网页有这样一张有意思的动画:从这张gif图片,我们不难看出Docker网站想传达这样一条信息, 使用Docker加速了build,ship和run的过程。Docker最早问世是2013年,以一个开源项目的方式被大家熟知。Docker的奠基者是dotcloud,一家开发PaaS平台的技术公司。不过可惜的是,这家公司把Docker开源之后,于2016年倒闭了,因为其主业务PaaS无法和微软,亚马逊等PaaS业界巨头竞争,不禁让人唏嘘。Docker其实是容器化技术的具体技术实现之一,采用go语言开发。很多朋友刚接触Docker时,认为它就是一种更轻量级的虚拟机,这种认识其实是错误的,Docker和虚拟机有本质的区别。容器本质上讲就是运行在操作系统上的一个进程,只不过加入了对资源的隔离和限制。而Docker是基于容器的这个设计思想,基于Linux Container技术实现的核心管理引擎。为什么资源的隔离和限制在云时代更加重要?在默认情况下,一个操作系统里所有运行的进程共享CPU和内存资源,如果程序设计不当,最极端的情况,某进程出现死循环可能会耗尽CPU资源,或者由于内存泄漏消耗掉大部分系统资源,这在企业级产品场景下是不可接受的,所以进程的资源隔离技术是非常必要的。我当初刚接触Docker时,以为这是一项新的技术发明,后来才知道,Linux操作系统本身从操作系统层面就支持虚拟化技术,叫做Linux container,也就是大家到处能看到的LXC的全称。LXC的三大特色:cgroup,namespace和unionFS。cgroup:CGroups 全称control group,用来限定一个进程的资源使用,由Linux 内核支持,可以限制和隔离Linux进程组 (process groups) 所使用的物理资源 ,比如cpu,内存,磁盘和网络IO,是Linux container技术的物理基础。namespace:另一个维度的资源隔离技术,大家可以把这个概念和我们熟悉的C++和Java里的namespace相对照。如果CGroup设计出来的目的是为了隔离上面描述的物理资源,那么namespace则用来隔离PID(进程ID),IPC,Network等系统资源。我们现在可以将它们分配给特定的Namespace,每个Namespace里面的资源对其他Namespace都是透明的。不同container内的进程属于不同的Namespace,彼此透明,互不干扰。我们用一个例子来理解namespace的必要。假设多个用户购买了一台Linux服务器的Nginx服务,每个用户在该服务器上被分配了一个Linux系统的账号。我们希望每个用户只能访问分配给其的文件夹,这当然可以通过Linux文件系统本身的权限控制来实现,即一个用户只能访问属于他本身的那些文件夹。但是有些操作仍然需要系统级别的权限,比如root,但我们肯定不可能给每个用户都分配root权限。因此我们就可以使用namespace技术:我们能够为UID = n的用户,虚拟化一个namespace出来,在这个namespace里面,该用户具备root权限,但是在宿主机上,该UID =n的用户还是一个普通用户,也感知不到自己其实不是一个真的root用户这件事。同样的方式可以通过namespace虚拟化进程树。在每一个namespace内部,每一个用户都拥有一个属于自己的init进程,pid = 1,对于该用户来说,仿佛他独占一台物理的Linux服务器。对于每一个命名空间,从用户看起来,应该像一台单独的Linux计算机一样,有自己的init进程(PID为1),其他进程的PID依次递增,A和B空间都有PID为1的init进程,子容器的进程映射到父容器的进程上,父容器可以知道每一个子容器的运行状态,而子容器与子容器之间是隔离的。从图中我们可以看到,进程3在父命名空间里面PID 为3,但是在子命名空间内,他就是1.也就是说用户从子命名空间 A 内看进程3就像 init 进程一样,以为这个进程是自己的初始化进程,但是从整个 host 来看,他其实只是3号进程虚拟化出来的一个空间而已。看下面的图加深理解。父容器有两个子容器,父容器的命名空间里有两个进程,id分别为3和4, 映射到两个子命名空间后,分别成为其init进程,这样命名空间A和B的用户都认为自己独占整台服务器。Linux操作系统到目前为止支持的六种namespace:unionFS:顾名思义,unionFS可以把文件系统上多个目录(也叫分支)内容联合挂载到同一个目录下,而目录的物理位置是分开的。要理解unionFS,我们首先要认识bootfs和rootfs。1. boot file system (bootfs):包含操作系统boot loader 和 kernel。用户不会修改这个文件系统。一旦启动完成后,整个Linux内核加载进内存,之后bootfs会被卸载掉,从而释放出内存。同样内核版本的不同的 Linux 发行版,其bootfs都是一致的。2. root file system (rootfs):包含典型的目录结构,包括 /dev, /proc, /bin, /etc, /lib, /usr, and /tmp就是我下面这张图里的这些文件夹:等再加上要运行用户应用所需要的所有配置文件,二进制文件和库文件。这个文件系统在不同的Linux 发行版中是不同的。而且用户可以对这个文件进行修改。Linux 系统在启动时,roofs 首先会被挂载为只读模式,然后在启动完成后被修改为读写模式,随后它们就可以被修改了。不同的Linux版本,实现unionFS的技术可能不一样,使用命令docker info查看,比如我的机器上实现技术是overlay2:看个实际的例子。新建两个文件夹abap和java,在里面用touch命名分别创建两个空文件:新建一个mnt文件夹,用mount命令把abap和java文件夹merge到mnt文件夹下,-t执行文件系统类型为aufs:sudo mount -t aufs -o dirs=./abap:./java none ./mntmount完成后,到mnt文件夹下查看,发现了来自abap和java文件夹里总共4个文件:现在我到java文件夹里修改spring,比如加上一行spring is awesome, 然后到mnt文件夹下查看,发现mnt下面的文件内容也自动被更新了。那么反过来会如何呢?比如我修改mnt文件夹下的aop文件:而java文件夹下的原始文件没有受到影响:实际上这就是Docker容器镜像分层实现的技术基础。如果我们浏览Docker hub,能发现大多数镜像都不是从头开始制作,而是从一些base镜像基础上创建,比如debian基础镜像。而新镜像就是从基础镜像上一层层叠加新的逻辑构成的。这种分层设计,一个优点就是资源共享。想象这样一个场景,一台宿主机上运行了100个基于debian base镜像的容器,难道每个容器里都有一份重复的debian拷贝呢?这显然不合理;借助Linux的unionFS,宿主机只需要在磁盘上保存一份base镜像,内存中也只需要加载一份,就能被所有基于这个镜像的容器共享。当某个容器修改了基础镜像的内容,比如 /bin文件夹下的文件,这时其他容器的/bin文件夹是否会发生变化呢?根据容器镜像的写时拷贝技术,某个容器对基础镜像的修改会被限制在单个容器内。这就是我们接下来要学习的容器 Copy-on-Write 特性。容器镜像由多个镜像层组成,所有镜像层会联合在一起组成一个统一的文件系统。如果不同层中有一个相同路径的文件,比如 /text,上层的 /text 会覆盖下层的 /text,也就是说用户只能访问到上层中的文件 /text。假设我有如下这个dockerfile:FROM debianRUN apt-get install emacsRUN apt-get install apache2CMD ["/bin/bash"]执行docker build .看看发生了什么。生成的容器镜像如下:当用docker run启动这个容器时,实际上在镜像的顶部添加了一个新的可写层。这个可写层也叫容器层。容器启动后,其内的应用所有对容器的改动,文件的增删改操作都只会发生在容器层中,对容器层下面的所有只读镜像层没有影响。要获取更多Jerry的原创文章,请关注公众号"汪子熙": ...

December 21, 2018 · 1 min · jiezi

将 vue spa 项目运行在 docker 的 nginx 容器中

将vue spa项目运行在docker的nginx容器中,步骤:1.安装docker2.下载nginx镜像([:tag]:是具体的nignx版本,比如::1.15.7;默认从 https://hub.docker.com/ 下载镜像):docker pull nginx[:tag]3.运行命令打包项目:npm run build4.编写nginx的配置文件(文件在本项目中位置:nginx/default.conf)5.在当前目录下运行 docker 命令([:tag]部分,需要替换成具体的值):docker run -p 9081:80 -v $PWD/dist/:/usr/share/nginx/dist/ -v $PWD/nginx/default.conf:/etc/nginx/conf.d/default.conf -d nginx[:tag]6.宿主机(就是本机)访问项目网址:http://localhost:9081/docker run命令参数说明:参数说明-v, –volume value:Bind mount a volume (default [])宿主机会覆盖容器内文件-p, –publish value:Publish a container’s port(s) to the host (default [])宿主机端口对应容器内端口-d, –detach:Run container in background and print container ID保持容器在后台持续运行;后续可以使用docker exec -it <容器名或容器id> bash,进入容器的bash命令项目例子:https://github.com/cag2050/vu…

December 20, 2018 · 1 min · jiezi

这样来轻松自定义 Jenkins 发行版!

今天,我打算给 Jenkins 管理员和开发者们介绍一个新的工具 Custom WAR Packager。该工具可以打包 Jenkins 的自定义 WAR 发行版、 Docker 镜像以及 Jenkinsfile Runner 包。它可以打包 Jenkins、插件以及配置为开箱即用的发行版。 Custom WAR Packager 是我们曾在一篇博客– A Cloud Native Jenkins –中介绍过的无状态 Jenkins master 工具链的一部分。这个工具链已在 Jenkins X 中被用于构建 serverless 镜像。在这篇文章中,我将会介绍几种 Custom WAR Packager 常见的使用场景。历史正如 Jenkins 本身一样,Custom WAR Packager 开始于一个小的开发工具。在 Jenkins 内运行集成测试很长时间以来都是一个难题。对此,我们有三个主要的框架: Jenkins Test Harness, Acceptance Test Harness, 和 Plugin Compatibility Tester。这些框架都需要一个 Jenkins WAR 文件来运行测试。但是,假如你想在类似 AWS 一样的自定义环境中进行 Jenkins 测试呢? 或者,你希望基于 Pluggable Storage 的环境也可以复用 Jenkins 流水线测试,来确保没有回归缺陷,又如何呢?这并不是没有意义的问题。云原生 Jenkins、Jenkins Evergreen 以及 Jenkins X, 这些 Jenkins 项目中正在进行的主要活动,都需要大量的集成测试来实现持续交付流程。为了复用已有的框架,我们需要打包一个自带配置的 WAR 文件,以便可以在现有的框架中运行集成测试。这正是 Custom WAR Packager 于 2018年4月 创建的原因。到 2018年9月,它相继支持了 Docker 镜像和 Jenkinsfile Runner,后者由 Kohsuke Kawaguchi 创建并由 Nicolas de Loof 完善。揭开面纱Custom WAR Packager 是一个工具,可以作为命令行、Maven 插件或者 Docker 程序包来用。该工具可以从用户处获取配置,并根据用户请求进行打包。所有内容都由一个 YAML 配置文件管理:该工具支持多种输入类型。插件列表可以来自 YAML,pom.xml 或一个 BOM(jep:309[] 提出的 Bill of Materials) 文件。Custom WAR Packager 不仅支持发布版本,还可以构建部署到 增量仓库 (Jenkins 核心及插件的 CD 流程 - jep:305[]),甚至直接从 Git 或指定目录中构建。它允许从任何来源构建包,而无需等待官方版本。由于插件已经通过 Commit ID 缓存到了本地的 Maven 仓库中,因此其构建过程也非常快。自定义Custom WAR Packager 还支持下面的配置选项:Jenkins 配置即代码 的 YAMl 文件Groovy Hooks (例如:预配置的 init hooks)系统属性WAR打包每当这个库构建时会打包出来一个 WAR 文件。通常,Custom WAR Packager 会根据下面对 Jenkins 核心和 JCasC 的配置把所有内容打包的一个 WAR 文件中。样例配置:bundle: groupId: “io.jenkins.tools.war-packager.demo"artifactId: “blogpost-demo"vendor: “Jenkins project"description: “Just a demo for the blogpost"war: groupId: “org.jenkins-ci.main"artifactId: “jenkins-war"source: version: 2.138.2plugins:groupId: “io.jenkins"artifactId: “configuration-as-code"source: # Common releaseversion: 1.0-rc2groupId: “io.jenkins"artifactId: “artifact-manager-s3"source: # Incrementalsversion: 1.2-rc259.c9d60bf2f88cgroupId: “org.jenkins-ci.plugins.workflow"artifactId: “workflow-job"source: # Gitgit: https://github.com/jglick/wor...commit: 18d78f305a4526af9cdf3a7b68eb9caf97c7cfbcetc.systemProperties: jenkins.model.Jenkins.slaveAgentPort: “9000"jenkins.model.Jenkins.slaveAgentPortEnforce: “true"groovyHooks:type: “init"id: “initScripts"source: dir: src/main/groovycasc🆔 “jcasc"source: dir: casc.ymlDocker 打包为了打包 Docker,Custom WAR Packager 使用官方的 Docker 镜像 jenkins/jenkins 或同样格式的其他镜像。在构建期间,WAR 文件会被该工具构建的文件所替换。这也就意味着镜像的 所有 特色在该自定义构建中都可用: plugins.txt, Java 选项, Groovy hooks 等等。…## WAR configuration from above## …buildSettings: docker: build: trueBase imagebase: “jenkins/jenkins:2.138.2"Tag to set for the produced imagetag: “jenkins/custom-war-packager-casc-demo"例如:示例 展示了打包带有将构建日志存储到 Elasticsearch 的 Docker 镜像。 尽管这些已经作为了 jep:207 和 jep:210 的一部分,你还是可以查看这个示例,了解该 Docker 镜像是如何配置、连接到 Elasicsearch、然后启动外部的日志存储,而不需要改变日志的界面。一个 Docker Compose 文件对于运行整个集群是必要的。Jenkinsfile Runner 打包这可能是 Jenkinsfile Runner 最微妙的模式。三月,在开发者列表中 宣布了一个新的项目 Jenkinsfile Runner。大体的思路是,支持在单一 master 上只运行一次并打印输出到控制台的 Jenkins 流水线。 Jenkinsfile Runner 作为命令或一个 Docker 镜像来运行。虽然只推荐 Docker 的形式,但是 Custom WAR Packager 都能够生成。使用 Jenkinsfile Runner ,你可以像下面的方式来运行流水线:docker run –rm -v $PWD/Jenkinsfile:/workspace/Jenkinsfile acmeorg/jenkinsfile-runner当我们开始在云原生特别兴趣小组(Cloud Native SIG)中研究无状态(也就是“一次”)时,有一个想法就是使用 Custom WAR Packager 和其他已有的工具(Jenkinsfile Runner, Jenkins Configuration as Code 等)来实现。也许只是替换 Jenkinsfile Runner 中的 Jenkins 核心的 JAR 以及插件,但这还不够。为了高效,Jenkinsfile Runner 镜像应该启动得 很快。在构建流程实现中,我们使用了 Jenkins 和 Jenkinsfile Runner 一些实验性的选项,包括:类加载预缓存、插件解压等等。有了这些后,Jenkins 使用 configuration-as-code 和几十个插件可以在几秒钟内启动。那么,如何构建自定义 Jenkinsfile Runner 镜像呢?尽管目前还没有发布,但这不会影响我们继续实现上文提到的内容。…## WAR Configuration from above##…buildSettings: jenkinsfileRunner: source: groupId: “io.jenkins"artifactId: “jenkinsfile-runner"build: noCache: truesource: git: https://github.com/jenkinsci/j … r.gitcommit: 8ff9b1e9a097e629c5fbffca9a3d69750097ecc4docker: base: “jenkins/jenkins:2.138.2"tag: “onenashev/cwp-jenkinsfile-runner-demo"build: true你可以从 这里 找到用 Custom WAR Packager 打包 Jenkinsfile Runner 的例子。更多信息还有很多其他的特色没有在本文中提到。例如:它还可以修改 Maven 构建配置或增加、替换 Jenkins 核心中的库(例如:Remoting)。请查看 Custom WAR Packager 文档 获取更多信息和示例。如果你有兴趣对这个库做贡献,请创建 PR 并抄送 @oleg-nenashev 和 Raul Arabaolaza。(编者注:Raul Arabaolaza 是第二位正在研究 Jenkins 自动化测试流程的维护者。)下一步还有很多值得改进的地方可以使这个工具更加高效:增加对插件依赖传递的检查以便在构建过程中发现冲突允许在 YAML 配置文件中设置各种系统属性和 Java 选项改进 Jenkinsfile Runner 的性能集成到 Jenkins 集成测试流程中,(查看 Jenkins 流水线库中的 essentialsTest())即使目前,该工具已经能够让 Jenkins 用户构建他们自己的发行版,从理论上来讲,仍有许多其他任务可以在 Custom WAR Packager 中实现。 ...

December 20, 2018 · 2 min · jiezi

Docker 验证 Centos7.2 离线安装 Docker 环境

序题记:搞定了就是故事,搞不定就是事故。条件有限,开局只有一台开发机,要跑5套子系统组成的项目群。还要有一些辅助和验证的系统要跑在这上面。从配置来看,要想顺畅开心地完成项目群基础环境支撑,考虑引入资源消耗1/200的神器Docker:更难得是丝毫不用担心环境污染、版本冲突、以及突然开发服务器搞崩溃了;吐槽避免采坑实际上可能要跑的系统和辅助系统远超预计申请的是4C/8G的PC server,后来查命令是1C/2G,10G空间,果断申请扩容了边干边写:整了四周;期间无数次崩溃,还要应对乙方内部的质疑,不知道自己是如何坚持下来的第一周折腾无实质交付MacBook 上docker环境起了 CentOS7.2 容器,在容器中模拟离线安装 Docker 环境(盗梦空间)安装很成功,按官方教程启动服务时,崩溃到怀疑人生:命令”systemctl start docker”报错:”docker Failed to get D-Bus connection”CentOS 从7开始,安全考虑,容器内默认不加载D-Bus:直接影响就是 systemctl 不能起服务;github 支招:加容器特权参数 –privileged ,启动非常之慢,不建议踩坑;参考链接Docker 官方支招:自己编译带 systemctl 服务的镜像 参考链接,然而我没能测试成功在容器中启动 docker daemo其他服务没尝试,感兴趣的可自行研究放弃在容器中使用 systemctl 启动 Docker 镜像服务第二周换 Redhad7.2镜像还是报同样的错误:直接报缺58个一级依赖包后来分析不是CentOS和Redhad不同步,应该是甲方提供的CentOS源或系统优化或缺了一些默认的包;第三周寄希望于甲方提供的内网源协调再三给挂了源:是一个docker集群监控的源;沟通不畅,不认为挂错了源;甲方老师又忙。暂停修整了一周;第四周重整旗鼓再三协调之后,放弃等待甲方老师的支援。决定还是自己搞。不就是一些一级依赖么,补全就是!主要是我方火力太猛:再不出活,无颜面对向兄弟们:虽然不上这些,采用笨办法也能干,但总归不忍再不交付,无法上得台面解释:自己选择的牛皮,哭着也要吹完,吹完美搞定。想来想去,还是毛选里的那句话支撑了我:自力更生,艰苦奋斗!最近在看一行禅师的《佛陀传》,希望能从中汲取心灵的力量。《天龙八部》里扫地僧也曾说过:只有佛法越高,慈悲之念越盛,武功绝技才能练得越我或许我辈须工作中修行,也是这个道理!采坑记对,就是采坑。以下是从草稿中摘取的部分尝试:编号容器1docker run -it –name dc7 ailyfeng/centos7.2.1511 /bin/bash2docker run -it –name dc88 sssllc/centos7.2-jdk1.8 /bin/bash3docker run -it –name ct7 centos /bin/bash4docker run -it –privileged –name dc99 sssllc/centos7.2-jdk1.85docker build –rm -t centos:systemd . && docker run -it –name dr7 centos:systemd6docker run -it –name dr18 yjjy0921/redhat7.2 /bin/bash几个搞定依赖包的有用命令ldd 应该是 linux 通用的命令;rpm 和 repotrack 貌似是 CentoOS 的专有命令;编号命令举例作用1lddldd wkhtmltopdfnot found的就是对应的依赖包不存在,适用于二进制包(wkhtmltopdf)的命令;而需要安装的命令(docker-ce)只能通过安装报错来捕获缺失的依赖了2rpm -qlrpm -ql docker-ce查看安装的时候有哪些命令在PATH下,用这些命令去启动3repotrackrepotrack -a x86_64 -p /usr/local/yumrepo打包下载指定架构(X86_63)所有的依赖到指定目录思路:先验证再服务器落地本地 pull一个CentOS7.2的镜像只下载 Docker及其依赖不安装安装验证考虑用户及用户组赋权cp 至服务器尝试安装 docker内网 Linux 基础依赖要安装 Docker 基础环境,必须满足:Linux 内核必须大于3.10:登录内网服务器看了看,恰好3.10。够用,就不折腾了;需要支持 device-mapper:已支持[root@pms tmp] uname -r3.10.0-327.e17.x86_64[root@pms tmp] ls -l /sys/class/misc/device-mapperlrwxrwxrwx. 1 root root 0 May 20 16:17 /sys/class/misc/device-mapper -> ../../devices/virtual/misc/device-mapper环境模拟MacBook 上,起 CentOS7.2 容器,来验证思路;拉取镜像,创建 repo 目录ChinaDreams: kangcunhua$ docker run -it –name dc88 sssllc/centos7.2-jdk1.8 /bin/bash[root@7d935562e0ae /]# java -versionjava version “1.8.0_111"Java(TM) SE Runtime Environment (build 1.8.0_111-b14)Java HotSpot(TM) 64-Bit Server VM (build 25.111-b14, mixed mode)[root@7d935562e0ae /]# yum repo listLoaded plugins: fastestmirror, ovlNo such command: repo. Please use /usr/bin/yum –help[root@7d935562e0ae /]# [root@7d935562e0ae /]# yum repolist…[root@7d935562e0ae home]# cd /usr/local/root@7d935562e0ae local]# mkdir yumrepo下载所需离线包[root@7d935562e0ae local]# cd yumrepo/[root@7d935562e0ae yumrepo]# yum install –downloadonly –downloaddir=/usr/local/yumrepo/ docker安装 createrepo[root@7d935562e0ae yum.repos.d]# yum install createrepo -y新建 docker.repo[root@7d935562e0ae yum.repos.d]# pwd/etc/yum.repos.d[root@7d935562e0ae yum.repos.d]# vi docker.repodocker.repo[docker-yum]name=dockeryumbaseurl=file:///usr/local/yumrepoenable=1gpgcheck=0生成 repo 索引主要是将索引生成在/usr/local/yumrepo/repodata目录[root@7d935562e0ae yum.repos.d]# createrepo /usr/local/yumrepo/[root@7d935562e0ae yum.repos.d]# cd /usr/local/yumrepo/[root@7d935562e0ae yumrepo]# ls…..docker-client-1.13.1-53.git774336d.el7.centos.x86_64.rpm repodata…..安装 docker 并验证[root@7d935562e0ae yumrepo]# yum –disablerepo=* –enablerepo=docker-yum install docker -y[root@7d935562e0ae yumrepo]# docker -vDocker version 1.13.1, build 774336d/1.13.1尝试启动 Hello-world此时会报错:提示 docker daemon 没有启动;[root@7d935562e0ae /]# docker run hello-world/usr/bin/docker-current: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?.See ‘/usr/bin/docker-current run –help’.启动 docker 守护进程使用sysytemctl启动服务。此时会报错,是CentOS7.2容器的“安全”考虑,没有启动D-Bus:虽然不那么友好;[root@7d935562e0ae /]# systemctl start docker Failed to get D-Bus connection: Operation not permitted在能查到的资料里,即使是官方,也没有说明 Docker Daemon 的守护命令放置在哪儿,以便我们能直接运行;后续在安装 Docker-CE 时我找到了这个命令:使用rpm -ql docker 查找该软件安装时在 PATH 下有哪些命令可以运行,成功找到了 Docker Daemon 所在:/usr/bin/dockerd 。详见文末参考;直接运行/usr/bin/dockerd[root@7d935562e0ae /]# /usr/bin/dockerd INFO[2018-05-22T11:55:22.811053980Z] libcontainerd: started new docker-containerd process pid=53INFO[0000] starting containerd module=containerd revision=773c489c9c1b21a6d78b5c538cd395416ec50f88 version=v1.0.3ERRO[0000] failed to change OOM score to -500 error=“write /proc/53/oom_score_adj: permission denied” module=containerd….仍旧报错,但至少验证了离线安装的思路是正确的。只是在 CentOS7.2 容器中无法启动 Docker 守护进程;结论经过数轮的尝试,我们可以得出结论:验证了离线安装的思路是正确的CentOS7.2 以及 RedHat7.2 容器中可以安装 docker 基础环境CentOS7.2 以及 RedHat7.2 容器中无法启动 Docker 守护进程容器中无法启动 Docker 守护进程的事,不影响我们的实验目标默认 yum install docker 的版本是 docker:Docker version 1.13.1现在 Docker 已变成 Docker-CE:18.03.1-ce,需要验证离线安装 Docker-CE安装 Docker-CE梳理思路,重来选择 centos7.2 基础镜像配置 docker-ce 源选择 downloadonly 的参数下载 docker-ce 及其依赖配置本地源指定本地源安装 docker-ce条件有限,就不容器中起 docker 服务了进内网,验证ChinaDreams:docker-systemctl kangcunhua$ docker run -it –name dc18 sssllc/centos7.2-jdk1.8 /bin/bash[root@557a4e0c3e7e /]#下载 centos7.2基础镜像ChinaDreams:docker-systemctl kangcunhua$ docker run -it –name dc18 sssllc/centos7.2-jdk1.8 /bin/bash[root@557a4e0c3e7e /]#配置 docker-ce 源如果不配置 docker-ce 源,默认安装的是 docker1.13。大概是两年前的版本了。后续 docker 官方将社区版本命名为 docker-ce。所以安装新版本,还是要配置下 yum 源的:这里强烈建议配置国内的,速度快。[root@557a4e0c3e7e /]# yum-config-manager –add-repo https://mirrors.ustc.edu.cn/docker-ce/linux/centos/docker-ce.repo配置后, yum search docker-ce 可以搜索到了。这时候就可以下载到指定位置:为了后续离线安装;下载不安装:为了验证离线[root@557a4e0c3e7e /]# cd /usr/local[root@557a4e0c3e7e local]# mkdir yumrepo[root@557a4e0c3e7e local]# yum install –downloadonly –downloaddir=/usr/local/yumrepo/ docker-ce配置 docker 本地源建立索引先安装本地 repo 索引创建工具,通过这个工具,建立索引:就是本地安装包所在目录下的 repodata 目录;[root@557a4e0c3e7e yumrepo]# yum install createrepo -y[root@557a4e0c3e7e yumrepo]# createrepo /usr/local/yumrepo/创建源文件 docker.repo[root@557a4e0c3e7e yumrepo]# cd /etc/yum.repos.d[root@557a4e0c3e7e yum.repos.d]# vi docker.repo[root@557a4e0c3e7e yum.repos.d]# more docker.repo [docker-yum]name=dockeryumbaseurl=file:///usr/local/yumrepoenable=1gpgcheck=0模拟离线安装yum –disablerepo=* –enablerepo=docker-yum install docker-ce -y验证安装成功[root@557a4e0c3e7e yum.repos.d]# docker -vDocker version 18.03.1-ce, build 9ee9f40启动 dockerd,报错这个是因为容器中又启动了 Docker daemon。部分报错日志节选:[root@557a4e0c3e7e /]# docker -vDocker version 18.03.1-ce, build 9ee9f40[root@557a4e0c3e7e /]# /usr/bin/dockerd …ERRO[0000] failed to change OOM score to -500 error=“write /proc/53/oom_score_adj: permission denied” module=containerd…address="/var/run/docker/containerd/docker-containerd.sock” module=“containerd/grpc"INFO[0000] containerd successfully booted in 0.029058s module=containerdERRO[2018-05-22T11:55:23.265326880Z] ‘overlay2’ is not supported over aufs …WARN[2018-05-22T11:55:23.353577680Z] Running modprobe xt_conntrack failed with message: ``, error: exit status 1 Error starting daemon: Error initializing network controller: error obtaining controller instance: failed to create NAT chain DOCKER: iptables failed: iptables -t nat -N DOCKER: iptables v1.4.21: can’t initialize iptables table `nat’: Permission denied (you must be root)Perhaps iptables or your kernel needs to be upgraded. (exit status 3)Copy 资源出来ChinaDreams:Desktop kangcunhua$ docker cp dc18:/usr/local/yumrepo .ChinaDreams:Desktop kangcunhua$ docker cp dc18:/etc/yum.repos.d/docker.repo .结论验证离线安装 Docker-CE 思路成功;容器中仍旧无法启动 Docker daemon;内网离线实战copy 资源到内网,通过 ssh 将依赖包传到服务器;登录服务器yumrepo 放置到 /usr/local/ 目录下docker.repo 放置到 /etc/yum.repo.d/ 目录下因为已经生成过索引了,所以无需再次运行 create repo验证 docker 本地源[root@pma03 ~]# yum repolist #可以成功查看到docker-yum离线安装[root@pma03 ]# yum –disablerepo=* –enablerepo=docker-yum install docker-ce -y报错,缺依赖包;docker-ce相关依赖包已经全了,只是有一些CentOS依赖的包版本比docker-ce低,内网系统可能优化精简“过”了,或基础软件不完整,导致这些低版本和高版本不能和谐共处;只能根据报错信息,去笔记本下载依赖;报错信息和分析详见文末:“附:依赖包冲突和解决”;解决依赖依旧是回到个人笔记本,联网下载依赖包:ChinaDreams: kangcunhua$ docker start dc7dc7[root@ce27b30d0d9e /]# yum-config-manager –add-repo https://download.docker.com/linux/centos/docker-ce.repo[root@ce27b30d0d9e /]# yum clean all[root@ce27b30d0d9e /]# yum makecache共计尝试两次,使用如下命令,下载补全系统依赖包:编号命令1repotrack -a x86_64 -p /usr/local/yumrepo docker-ce2repotrack -a x86_64 -p /usr/local/yumrepo glibc-2.17-105.e3repotrack -a x86_64 -p /usr/local/yumrepo systemd-sysv4repotrack -a x86_64 -p /usr/local/yumrepo dracut-network5repotrack -a x86_64 -p /usr/local/yumrepo libgudev16repotrack -a x86_64 -p /usr/local/yumrepo dracut-config-rescue7repotrack -a x86_64 -p /usr/local/yumrepo systemd-python8repotrack -a x86_64 -p /usr/local/yumrepo libstdc++9repotrack -a x86_64 -p /usr/local/yumrepo glibc-headers10repotrack -a x86_64 -p /usr/local/yumrepo pcre-devel11repotrack -a x86_64 -p /usr/local/yumrepo gcc-c++12repotrack -a x86_64 -p /usr/local/yumrepo glibc-devel13repotrack -a x86_64 -p /usr/local/yumrepo libtool-ltdl14repotrack -a x86_64 -p /usr/local/yumrepo libselinux-devel15repotrack -a x86_64 -p /usr/local/yumrepo libsepol-devel笔记本上验证保证这些依赖包,不会导致安装失败。[root@ce27b30d0d9e /]# yum –disablerepo=* –enablerepo=docker-yum install docker -y安装和配置用户权限安装将依赖包上传到内网服务器指定目录/usr/local/yumrepo ;[root@pma03 ~]# yum –disablerepo=* –enablerepo=docker-yum install docker-ce -y没有报错!验证[root@pma03 ~]# docker -vDocker version 18.03.1-ce, build 9ee9f40启动服务[root@pma03 ~]# systemctl start docker搞定!配置用户添加一个用户dev,专门管理docker。千万不要图省事,服务器上直接用root管理docker;容器可以;[root@pma03 ~]# cat /etc/group[root@pma03 ~]# cat /etc/group | grep docker[root@pma03 ~]# more /etc/passwd[root@pma03 ~]# gpasswd -a dev docker[root@pma03 ~]# useradd -g docker dev[root@pma03 ~]# passwd dev[root@pma03 ~]# chmod -v u+w /etc/sudoers[root@pma03 ~]# vi /etc/sudoers[root@pma03 ~]# chomod -v u-w /etc/sudoers[root@pma03 ~]# systemctl restart docker[root@pma03 ~]# docker -v[root@pma03 ~]# docker info[root@pma03 ~]# su dev[dev@pma03 ~]#安装 docker-compose又被坑了:docker for Mac、docker for windows 安装完都自带 docker-compose,linux 版本的 docker-ce 居然不带。想起来离线安装各种坑就头大,网上查了半天资料,居然是个 python 工具,要先安装 pip,o No!只好翻到 github,看看能不能源码编译安装。结果看到有 release 的下载,猜想可以直接使用。后来用下载后的文件百度,果然翻到一篇指南,可以这样搞:方法四:离线安装下载 docker-compose-Linux-x86_64,然后重新命名添加可执行权限即可:笔记本验证先ChinaDreams:Desktop kangcunhua$ docker cp ./docker-compose-Linux-x86_64.dms dc18:/usr/local/bin/docker-compose进入容器[root@557a4e0c3e7e /]# cd /usr/local/bin/[root@557a4e0c3e7e bin]# lsdocker-compose[root@557a4e0c3e7e bin]# chmod +x /usr/local/bin/docker-compose[root@557a4e0c3e7e bin]# docker-compose -vdocker-compose version 1.21.2, build a133471内网安装 docker-compose同样把安装包 copy 进内网,ssh 上传到服务器cp 重命名到指定目录赋予执行权限赋予 dev:docker 也有执行权限[root@pma03 ~]# cd /usr/local/bin/[root@pma03 bin]# cp /home/dev/docker-images/docker-compose-Linux-x86_64.dms docker-compose[root@pma03 bin]# chmod +x ./docker-compose[root@pma03 bin]# docker-compose -v[root@pma03 bin]# chown -R dev:docker ./docker-compose[root@pma03 bin]# su dev[dev@pma03 ]# docker-compose -v搞定!后续后续要做的事情就简单了:从笔记本上 pull 镜像,然后导出copy 到内网上传到服务器,导入docker run 起镜像docker-compose 起多个镜像尽情 happy 吧!附:依赖包冲突和分析内网离线安装碰上的依赖包问题和分析报错yun install docker-ce 时,提示有依赖包版本冲突 or 缺失;Error: Package: systemd-sysv-219-19.el7.x86_64 (@anaconda) Requires: systemd = 219-19.el7 Removing: systemd-219-19.el7.x86_64 (@anaconda) systemd = 219-19.el7 Updated By: systemd-219-42.el7_4.4.x86_64 (localyum) systemd = 219-42.el7_4.4Error: Package: dracut-network-033-359.el7.x86_64 (@anaconda) Requires: dracut = 033-359.el7 Removing: dracut-033-359.el7.x86_64 (@anaconda) dracut = 033-359.el7 Updated By: dracut-033-502.el7.x86_64 (localyum) dracut = 033-502.el7Error: Package: libgudev1-219-19.el7.x86_64 (@anaconda) Requires: systemd-libs = 219-19.el7 Removing: systemd-libs-219-19.el7.x86_64 (@anaconda) systemd-libs = 219-19.el7 Updated By: systemd-libs-219-42.el7_4.4.x86_64 (localyum) systemd-libs = 219-42.el7_4.4Error: Package: dracut-config-rescue-033-359.el7.x86_64 (@anaconda) Requires: dracut = 033-359.el7 Removing: dracut-033-359.el7.x86_64 (@anaconda) dracut = 033-359.el7 Updated By: dracut-033-502.el7.x86_64 (localyum) dracut = 033-502.el7Error: Package: systemd-python-219-19.el7.x86_64 (@anaconda) Requires: systemd = 219-19.el7 Removing: systemd-219-19.el7.x86_64 (@anaconda) systemd = 219-19.el7 Updated By: systemd-219-42.el7_4.4.x86_64 (localyum) systemd = 219-42.el7_4.4 You could try using –skip-broken to work around the problem You could try running: rpm -Va –nofiles –nodigest看到报错的时候,内心是崩溃的。最后还是收拾心情,逐一解决了。网上唯一可以找到的资料就是这篇离线安装docker包冲突,报错信息一模一样,但是帖子没有给出确定的原因和解决办法;但是给予了我信心,确实不是因为CentOS7.2和Redhat7.2有底层不同,导致的这些错误,这样是无力解决的;后续解决完依赖后,我猜测原因是,甲方提供的Redhat或CentOS,默认软件包安装的有问题:做了一些精简“优化”处理不了部分依赖包冲突;后续找到了这篇文章:CentOS7.2离线安装docker-ce最新版,文中指出Docker需要的部分软件包版本要高于CentOS7;就是说同样的软件包,Docker依赖的高版本的,但是CentOS依赖低版本的;解决办法就是找到他们,全部安装上去;下载 libgudev1 和 systemd-sysv,是因为 centos7.2 的 libgudev1 和 systemd-sysv 依赖 systemd-219-19.el7.x86_64,而 docker-ce 需要 systemd-219-30el7.x86_64.再次尝试repotrack -a x86_64 解决上述依赖后(命令详见正文:解决依赖),copy 进内网,再次尝试安装 Docker-CE。Error: Package: systemd-sysv-219-19.el7.x86_64 (@anaconda) Requires: systemd = 219-19.el7 Removing: systemd-219-19.el7.x86_64 (@anaconda) systemd = 219-19.el7 Updated By: systemd-219-42.el7_4.4.x86_64 (localyum) systemd = 219-42.el7_4.4Error: Package: dracut-network-033-359.el7.x86_64 (@anaconda) Requires: dracut = 033-359.el7 Removing: dracut-033-359.el7.x86_64 (@anaconda) dracut = 033-359.el7 Updated By: dracut-033-502.el7.x86_64 (localyum) dracut = 033-502.el7Error: Package: libgudev1-219-19.el7.x86_64 (@anaconda) Requires: systemd-libs = 219-19.el7 Removing: systemd-libs-219-19.el7.x86_64 (@anaconda) systemd-libs = 219-19.el7 Updated By: systemd-libs-219-42.el7_4.4.x86_64 (localyum) systemd-libs = 219-42.el7_4.4Error: Package: dracut-config-rescue-033-359.el7.x86_64 (@anaconda) Requires: dracut = 033-359.el7 Removing: dracut-033-359.el7.x86_64 (@anaconda) dracut = 033-359.el7 Updated By: dracut-033-502.el7.x86_64 (localyum) dracut = 033-502.el7Error: Package: systemd-python-219-19.el7.x86_64 (@anaconda) Requires: systemd = 219-19.el7 Removing: systemd-219-19.el7.x86_64 (@anaconda) systemd = 219-19.el7 Updated By: systemd-219-42.el7_4.4.x86_64 (localyum) systemd = 219-42.el7_4.4解决办法就是继续回到笔记本联网下载这些依赖包;详见:“解决依赖”;参考直接安装 rpm 包可以的,直接敲 “rpm -ivh 包名”但是有些rpm包是有依赖性的,可以在命令尾端添加 “–force–nodeps”,“–force”指强制“,”–nodeps“指不查找依赖性比如”rpm -ivh *.rpm –force—nodeps“,同时,也可以使用yum命令,会自动解决包依赖的关系,能便于管理大量系统的更新问题,建议使用yum容器特权–privilegedRuntime privilege, Linux capabilities, and LXC configuration–cap-add : Add Linux capabilities–cap-drop : Drop Linux capabilities–privileged=false : Give extended privileges to this container–device=[] : Allows you to run devices inside the container withoutthe –privileged flag.–lxc-conf=[] : (lxc exec-driver only) Add custom lxc options–lxc-conf=”lxc.cgroup.cpuset.cpus = 0,1”默认情况下,Docker的容器是没有特权的,例如不能在容器中再启动一个容器。这是因为默认情况下容器是不能访问任何其它设备的。但是通过”privileged”,容器就拥有了访问任何其它设备的权限。我尝试了下,启动非常之慢:不推荐ChinaDreams: kangcunhua$ docker run -it –privileged –name dc99 sssllc/centos7.2-jdk1.8 /usr/sbin/initWelcome to CentOS Linux 7 (Core)![ OK ] Reached target Swap.[ OK ] Created slice Root Slice.[ OK ] Listening on udev Control Socket.[ OK ] Reached target Encrypted Volumes.[ OK ] Listening on udev Kernel Socket.[ OK ] Listening on Delayed Shutdown Socket.[ OK ] Listening on /dev/initctl Compatibility Named Pipe.[ OK ] Reached target Remote File Systems.[ OK ] Created slice User and Session Slice.[ OK ] Created slice System Slice.[ OK ] Created slice system-serial\x2dgetty.slice.[ OK ] Reached target Slices.[ OK ] Listening on Journal Socket. Mounting Debug File System… Starting Journal Service… Mounting FUSE Control File System… Starting Apply Kernel Variables… Starting Create Static Device Nodes in /dev… Mounting Huge Pages File System… Starting Setup Virtual Console…[ OK ] Created slice system-getty.slice. Starting Remount Root and Kernel File Systems…[ OK ] Reached target Paths.[ OK ] Mounted FUSE Control File System.[ OK ] Mounted Debug File System.[ OK ] Mounted Huge Pages File System.[ OK ] Started Apply Kernel Variables.[ OK ] Started Setup Virtual Console.[ OK ] Started Create Static Device Nodes in /dev. Starting udev Kernel Device Manager…[ OK ] Started Journal Service.[ OK ] Started udev Kernel Device Manager.[FAILED] Failed to start Remount Root and Kernel File Systems.See ‘systemctl status systemd-remount-fs.service’ for details.[ OK ] Reached target Local File Systems (Pre). Starting Rebuild Hardware Database… Starting Load/Save Random Seed… Starting Flush Journal to Persistent Storage…[ OK ] Reached target Local File Systems. Starting Rebuild Journal Catalog…[ OK ] Started Flush Journal to Persistent Storage. Starting Create Volatile Files and Directories…[ OK ] Started Load/Save Random Seed.[ OK ] Started Rebuild Journal Catalog.[ OK ] Started Create Volatile Files and Directories. Starting Update UTMP about System Boot/Shutdown…[ OK ] Started Update UTMP about System Boot/Shutdown.[ OK ] Started Rebuild Hardware Database. Starting udev Coldplug all Devices… Starting Update is Completed…[ OK ] Started Update is Completed.[ OK ] Started udev Coldplug all Devices.[ OK ] Reached target System Initialization.[ OK ] Reached target Timers.[ OK ] Listening on D-Bus System Message Bus Socket.[ OK ] Reached target Sockets.[ OK ] Reached target Basic System. Starting LSB: Supports the direct execution of binary formats…. Starting Permit User Sessions…[ OK ] Started D-Bus System Message Bus. Starting D-Bus System Message Bus… Starting Login Service…[ OK ] Started Permit User Sessions. Starting Cleanup of Temporary Directories…[ OK ] Started Getty on tty1. Starting Getty on tty1…[ OK ] Started Cleanup of Temporary Directories.[ OK ] Started Login Service.[ OK ] Started LSB: Supports the direct execution of binary formats..[ TIME ] Timed out waiting for device dev-ttyS0.device.[DEPEND] Dependency failed for Serial Getty on ttyS0.[ OK ] Reached target Login Prompts.[ OK ] Reached target Multi-User System. Starting Update UTMP about System Runlevel Changes…[ OK ] Started Update UTMP about System Runlevel Changes.^C^C^C^C^C^Cc\c/CentOS 安装 Docker CE参考链接$ sudo yum-config-manager \ –add-repo \ https://mirrors.ustc.edu.cn/docker-ce/linux/centos/docker-ce.repo$ sudo yum-config-manager –enable docker-ce-edge$ sudo yum makecache fast$ sudo yum install docker-ce知识点rpm -ql 软件包rpm -ql 软件包 查看安装的时候有哪些命令在 PATH 下,用这些命令去启动,这个是一种解决的方法[root@9c676d901d7b bin]# rpm -ql docker-ce/etc/udev/rules.d/80-docker.rules/usr/bin/docker/usr/bin/docker-containerd/usr/bin/docker-containerd-ctr/usr/bin/docker-containerd-shim/usr/bin/docker-init/usr/bin/docker-proxy/usr/bin/docker-runc/usr/bin/dockerd/usr/lib/systemd/system/docker.service/usr/share/bash-completion/completions/dockerDocker+K8s本文由作者:蛮大人 授权发布链接:https://opsdev.fun/2018/05/09…著作权归作者所有。转载请联系作者获得授权 ...

December 19, 2018 · 8 min · jiezi

大家好 这就是2018年的我~

大家好,今天周五,明天就是周末,再过几天也就是2019,2018即将成为过去,昨晚抽时间对自己的2018做了个年终总结,今天跟大家汇报一下。以下就是2018年的我:首先请看看我的钱包接下来就是我的积蓄然后就是我的体重还有大家比较关心的我的睡眠质量啦啦~ 我的发量,默默问一句,你的有多少?我对生活的态度佛系青年有没有,是不是很棒棒??我的感情经历相信自己的眼睛,你没有看错、网络也正常??我的生活经历字不重要,图是重点??我的求知欲学习使我进步,我对知识的渴望……我的夜生活身处一线城市,丰富多彩的夜生活……抱歉 上面那张图是我比较期望的这才是我的夜生活~我的理想残酷的现实我的养生理念人人都爱美,我也有自己的养生理念我的自信心我的上班状态热爱工作的我,干劲十足~我的下班状态好烦,怎么这么快就下班了~开会时的我听到八卦的我我的朋友圈年初的我新的一年开始了,一定要大展身手年终的我总的来说我的2018年大概是这样的这样的这样的这样的和这样的真是丰富而又充实的一年呢!好啦,2018终于过完啦,大概就是这样,2019,请千万对我好一点啊~~2019,请千万对我好一点啊~~今年的冬天特别的冷,希望此文能博你一笑,如果你喜欢的话,欢迎点赞、关注,分享给你的好友们就更棒了,关注公号(IT平头哥联盟)谢谢支持~其他vuereactjava等资源共享 团队解散,我们该何去何从?月入三万 还能少了你一个鸡蛋如何给localStorage设置一个有效期?作者:苏南 - 首席填坑官链接:http://susouth.com/交流:912594095、公众号:honeyBadger8本文原创,著作权归作者所有。商业转载请联系@IT·平头哥联盟获得授权,非商业转载请注明原链接及出处。

December 17, 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

说说docker run的--detach

当我们使用了docker的run命令运行一个新容器,然后也发现他端端正正的呆在我们的容器列表中,但是就是无法使用exec命令,这是什么原因呢?$docker docker ps -aCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES1c8a61e5e09b php:latest “docker-php-entrypoi…” 10 minutes ago Up 10 minutes php如图我们看到,已经存在了这个容器,但如果这个时候使用exec就会报错,既没有启动这个容器。$docker start 1c8a61e5e09b$docker exec -it 1c8a61e5e09b /bin/bashError response from daemon: Container 1c8a61e5e09b is not running但是如果我们继续使用start命令的话,他就会提示该容器已经启动了,那这又是什么原因呢?因为我们单独的使用run只会启动容器,他会立即启动,相应然后就自动消失。你在这个时候使用exec命令已经太迟了。所以,当我们启动容器的时候一定要加上–detach或者-d来保持容器在后台持续运行。那么我们重新来一次。$docker run -d -P php:lateste63e06b3e66alasjdblqibeiqj1c8a61e5e09be7b996ec58a66438ee4e12db7f4d85189b21# exec命令$docker exec -it e63e06b3e66a /bin/bash -c “php –version"PHP 7.2.12 (cli) (built: Nov 16 2018 03:17:59) ( NTS )Copyright (c) 1997-2018 The PHP GroupZend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies这样就可以解决问题了。

December 14, 2018 · 1 min · jiezi

如何优化Docker储存

各位同学,大家在使用Docker的过程中,有没有想过,Docker在本地存储镜像时把文件存储在哪里了呢?有没有对文件的总大小做一定的限制呢?能不能调整本地存储的位置及总限制大小呢?今天,我们就从这些问题入手,来讨论一下Docker的存储优化方案。一、Docker的默认存储策略Docker提供了查看配置信息的命令,即docker info命令,通过该命令可以查看Docker的各种系统层面的信息,如当前运行的容器数、镜像数、Docker版本等信息,其中就包括了存储信息。我们输入docker info命令后,可得到类似如下的内容:Storage Driver: devicemapperPool Name: docker-253:2-923803-poolPool Blocksize: 65.54 kBBase Device Size: 10.74 GBBacking Filesystem: xfsData file: /dev/loop0Metadata file: /dev/loop1Data Space Used: 12.9 GBData Space Total: 107.4 GBData Space Available: 19.75 GBMetadata Space Used: 21.77 MBMetadata Space Total: 2.147 GBMetadata Space Available: 2.126 GBThin Pool Minimum Free Space: 10.74 GBUdev Sync Supported: trueDeferred Removal Enabled: falseDeferred Deletion Enabled: falseDeferred Deleted Device Count: 0Data loop file: /var/lib/docker/devicemapper/devicemapper/dataWARNING: Usage of loopback devices is strongly discouraged for production use. Use --storage-optdm.thinpooldev to specify a custom block storage device.Metadata loop file: /var/lib/docker/devicemapper/devicemapper/metadataLibrary Version: 1.02.140-RHEL7 (2017-05-03)其中,Data Space Total 即默认的最大储存空间,约为100G;Data loop file即默认的镜像存储路径,可以看到默认设置在/var/lib/docker路径下。值得一提的是,存储目录下的devicemapper/devicemapper/data文件是docker创建的稀疏文件,该文件在创建时即指定了大小,未真正使用的部分被系统由0填充,并且在磁盘统计时不计入使用磁盘大小。同时,该文件的大小即为Docker的最大储存空间。-rw——- 1 root root 100G Nov 8 10:11 data根据实际情况,有时需要扩大或者缩小Docker的最大储存空间,下面介绍一下具体的方法,并测试Docker的最大储存空间过小,对于Docker的影响,以及相关的建议。二、修改存储位置及储存空间一般情况下,Docker的配置文件的位置为:/etc/systemd/system/docker.service.d/docker.conf若无此文件可手动创建。此文件的内容可参照如下配置填写:[Service]ExecStart=ExecStart=/usr/bin/dockerd –storage-driver=devicemapper –insecure-registry 0.0.0.0/0 –registry-mirror http://dockerhub.yonyou.com -g /data/docker/其中,利用-g参数即可指定存储挂载路径。比如,示例中的配置将存储目录挂载在/data/docker/路径下。若想修改Docker的最大储存空间,也需要通过修改此配置文件实现。扩大存储空间扩大Docker的最大储存空间,直接修改配置文件即可。假设需要将Docker的最大储存空间扩大到200G,则具体的方法为:1、停止docker服务停止docker服务的命令如下。systemctl stop docker2、修改配置文件在Docker的配置文件最后一行的末尾添加——storage-opt dm.loopdatasize=200G,添加完之后的配置为:[Service]ExecStart=ExecStart=/usr/bin/dockerd –storage-driver=devicemapper –insecure-registry 0.0.0.0/0 –registry-mirror http://dockerhub.yonyou.com -g /data/docker/ –storage-opt dm.loopdatasize=200G3、重启docker服务重启docker的命令如下。systemctl daemon-reload && systemctl start docker重启之后,查看Docker的最大储存空间:…Data Space Used: 1.09 GBData Space Total: 214.7 GBData Space Available: 22.36 GBMetadata Space Used: 1.753 MBMetadata Space Total: 2.147 GBMetadata Space Available: 2.146 GB…Data loop file:/data/docker/devicemapper/devicemapper/data可以看到,最大存储空间的配置已经生效,当前约为200G.我们在配置中同时配置了挂载目录,可以看到,当前docker的存储文件也改变至/data/docker目录下。缩小存储空间我们首先想要提醒您的是,缩小Docker存储空间需要清空Docker的工作目录才能生效,清空Docker的工作目录会导致所有数据丢失。一定要确认本地所有镜像均已备份或可丢弃后,才可进行缩小存储空间操作。假设要将Docker的最大储存空间缩小为50G,具体的方法为:1、停止docker服务同样的,先使用命令停止docker服务。systemctl stop docker2、修改配置文件在Docker的配置文件最后一行的末尾添加——storage-opt dm.loopdatasize=50G,添加完之后的配置为:[Service]ExecStart=ExecStart=/usr/bin/dockerd –storage-driver=devicemapper –insecure-registry 0.0.0.0/0 –registry-mirror http://dockerhub.yonyou.com -g /data/docker/ –storage-opt dm.loopdatasize=50G3、删除Docker的工作目录请再次注意,清空Docker的工作目录会导致所有数据丢失,包括在本地保存的所有Docker镜像。rm -rf /data/docker4、重启docker服务systemctl daemon-reload && systemctl start docker重启之后,查看Docker的最大储存空间:Data Space Used: 11.8 MBData Space Total: 53.69 GBData Space Available: 23.44 GB可以看到,docker的存储空间已缩小至50G左右。那么我们可以思考一下,如果Docker的储存空间过小,会对我们使用Docker产生什么样的影响呢?储存空间过小的影响为了测试Docker的最大储存空间过小对Docker的影响,将Docker的最大储存空间设置为2G。下载四个镜像,分别为:REPOSITORY TAG IMAGE ID CREATED SIZEalpine latest 053cde6e8953 4 days ago 3.96 MBapps latest 67ea7f76e6db 5 days ago 687 MBjenkins latest ec714cdad606 3 months ago 975 MBdclb latest 483ca54282f0 4 months ago 44 MB使用docker info查看Docker存储空间的使用情况:Data Space Used: 1.935 GBData Space Total: 2.147 GBData Space Available:212.3 MB在Data Space Available项中,可以看到仅剩余200M左右存储空间。若此时下载其他镜像,则可能会报如下错误:failed to register layer: devmapper: Thin Pool has 3190 free data blocks which is less than minimum required 3276 free data blocks. Create more free space in thin pool or use dm.min_free_space option to change behavior如果创建一个容器,如alpine,则也可能会报如下错误:docker: Error response from daemon: devmapper: Thin Pool has 3239 free data blocks which is less than minimum required 3276 free data blocks. Create more free space in thin pool or use dm.min_free_space option to change behavior.通过测试,可知在删除一个镜像并释放空间之后,可以重新拉取小于此镜像大小的镜像,或者也可以成功创建alpine容器。三、优化Docker的存储策略在日常使用Docker服务时,我们给出以下两点建议:1)、每天定时清理不用的Docker文件;2)、将Docker的工作目录设置在数据挂载磁盘文件夹里面,且设置磁盘大于100G.优化建议一:定时清理Docker文件使用docker-gc可以清理Docker文件,docker-gc的清理规则是:·Containers that exited more than an hour ago are removed(删除已经退出一小时的容器)·Images that don’t belong to any remaining container after that are removed(删除不属于任何容器的镜像)运行如下命令,每天定时使用docker-gc清理Docker文件:sudo echo ‘0 2 * docker run –rm -v /var/run/docker.sock:/var/run/docker.sock -v /etc:/etc:ro -e FORCE_IMAGE_REMOVAL=1 spotify/docker-gc’ >> /var/spool/cron/root其中:FORCE_IMAGE_REMOVAL=1代表清理重复的镜像。优化建议二:迁移Docker工作目录在Docker的工作目录里面,会有很多大文件,如果将Docker的工作目录存放在根目录的话,会导致根目录很快被占满,因此需要将Docker的工作目录迁移到挂载的数据磁盘。假设要将Docker的工作目录从/var/lib/docker迁移到/data/docker目录下,迁移的方法为:1、暂停Docker服务systemctl stop docker2、迁移工作目录的数据创建docker工作目录,如果/data/docker已经存在,则可以跳过此步骤,直接复制文件:mkdir /data/docker迁移数据:cp -rf /var/lib/docker/* /data/docker/3.修改Docker的工作目录Docker的启动配置文件位置在:/etc/systemd/system/docker.service.d/docker.conf无此文件可手动创建。打开此文件,在其后面增加-g /data/docker,如果已经有-g选项,则直接修改后面的目录即可。修改后的结果如下:[Service]ExecStart=ExecStart=/usr/bin/dockerd –storage-driver=devicemapper –insecure-registry 0.0.0.0/0 –registry-mirror http://dockerhub.yonyou.com –g /data/docker/4、启动Docker服务systemctl daemon-reload && systemctl start docker5、删除源文件rm -rf /var/lib/docker/*以上就是Docker储存的优化方案,希望对您能够有所帮助,更好的使用Docker容器技术。 ...

December 14, 2018 · 2 min · jiezi

jenkins自动化项目部署实战

jenkins自动化项目部署实战简介以下文章只是从入门来说明jenkins的部署过程,仅供新手入门,高手勿喷。安装命令如下:拉镜像,无需解释docker pull jenkins创建挂载路径mkdir /mnt/jenkinschown -R 1000 /mnt/jenkins8080: 访问网页;50000: 配置主从,在slave上构建需映射50000docker run –name jenkins -p 8080:8080 -p 50000:50000 -v /mnt/jenkins:/var/jenkins_home jenkins注:暴露端口根据需要自定义修改。初始密码cat /mnt/jenkins/secrets/initialAdminPassword安装推荐插件(前提:服务器配置安全组,开放暴露端口)访问网页,输入密码,默认以admin进入,会显示推荐插件安装。另外,Maven项目需要另外下载一个插件方能支持(主要体现在创建项目时,出现Maven选项):Maven Integration plugin坑点:自备梯子,有时网络不佳会导致下载安装失败,可自行截图记住插件,以便之后进入插件管理页面重新下载配置系统管理配置Jenkins主页 - 系统管理 - 管理插件安装如下插件:Maven Integration pluginJenkins主页 - 系统管理 - Global Tool ConfigurationAdd Mavenssh连接1:服务器本地 && docker容器进入Docker容器,生成 ssh keycopy id_rsa.pub 到服务器本机~/.m2/authorized_keysdocker exec -it jenkins bashssh-keygencat ~/.ssh/id_rsa.pubecho " id_rsa.pub " >> ~/.m2/authorized_keysssh连接2:与Git版本管理工具连接(常见如:Gitlab,Github)以本人配置的Github举栗子:进入Github,添加 ssh key (docker容器里的 id_ras.pub )项目基础配置配置Credentials常见问题问题一:No valid crumb was included in the request.解决方案去掉“防止跨站点请求伪造”选项。问题二:Host key verification failed.解决方案进入docker容器,执行如下命令:root@IP注:IP依脚本而定。结语至此,整个docker安装和项目发布过程就描述到这里了,希望对大家有所帮助。俊龙-广州芦苇科技Java工程师

December 14, 2018 · 1 min · jiezi

Docker镜像批量清理之道

使用jenkins作为打包的工具,主机上的磁盘空间总是被慢慢被占满,直到jenkins无法运行。本文从几个方面来清理docker垃圾。批量删除已经退出的容器docker ps -a | grep “Exited” | awk ‘{print $1 }’ | xargs docker rm批量删除带有none字段的镜像$3一般就是取出每一行的镜像id字段# 方案1: 根据镜像id删除镜像docker images| grep none |awk ‘{print $3 }’|xargs docker rmi# 方案2: 根据镜像名删除镜像docker images | grep wecloud | awk ‘{print $1":"$2}’ | xargs docker rmi方案1,根据镜像ID删除镜像时,有写镜像虽然镜像名不同,但是镜像ID都是相同的,这是后往往会删除失败。所以根据镜像名删除镜像的效果会更好。删除镜像定时任务脚本#!/bin/bash# create by wangduanduan# when current free disk less then max free disk, you can remove docker images#GREEN=’\033[0;32m’RED=’\033[0;31m’NC=’\033[0m’max_free_disk=5 # 5G. when current free disk less then max free disk, remove docker imagescurrent_free_disk=df -lh | grep centos-root | awk '{print strtonum($4)}'df -lhecho “max_free_disk: $max_free_disk G"echo -e “current_free_disk: ${GREEN} $current_free_disk G ${NC}“if [ $current_free_disk -lt $max_free_disk ]then echo -e “${RED} need to clean up docker images ${NC}” docker images | grep none | awk ‘{print $3 }’ | xargs docker rmi docker images | grep wecloud | awk ‘{print $1”:"$2}’ | xargs docker rmielse echo -e “${GREEN}no need clean${NC}“fi注意事项为了加快打包的速度,一般不要太频繁的删除镜像。因为老的镜像中的某些不改变的层,可以作为新的镜像的缓存,从而大大加快构建的速度。 ...

December 13, 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

峰采 2018-12-09

开篇原创的文件实在没有时间写了。所以这个是一个文章,链接推荐贴。有点像阮老师的weekly。但是暂时不会是每周都有的。毕竟不是专职做这个的。内容嘛,没有限制。但多数是技术相关的。正文https://www.linuxjournal.com/…Chrome OS稳定版可以直接运行Linux Apps了。Windows 已经可以 apt-get了。Chrome OS现在也可以了。商业巨头在帮桌面Linux续命,而Ubuntu已经将重心转向了cloud。https://wetransfer.com/方便的传送大型文件https://www.cacher.io/方便快捷的代码短片记录。还是gist好?
https://www.bloomberg.com/amp…IBM收购红帽。$190 per share啊,要是我我也卖了。已是旧闻。但前几天有rumor说Google要截胡。应该不会吧。不管是谁买,一件事是可以肯定的:open source is eating the worldhttps://github.com/ncw/rclone类似Dropbox的开源文件同步软件。好处是对接各种云存储,包括国内的青云。做图床或者私有备份都可以。https://blog.cloudflare.com/c…技术就是用来被颠覆的。当容器,kubernetes在颠覆云计算的时候,cloudflare已经在颠覆容器了。这东西有个名字叫Worker。再过几年,也许大家就不再用docker, k8s说事了。。。。到时候我吃什么喝什么呢?https://www.slidescarnival.co…免费,好看的slides模板。支持Google Slides和Powerpointhttps://www.gatesnotes.com/Ab…比尔盖茨喜欢看《硅谷》。认为其中很多内容很真实。《硅谷》确实NB,我最喜欢的美剧之一。码农必刷。https://2018.stateofjs.com/co…2018 Javascript 报告https://nginxconfig.io流行软件里面有必Nginx的配置更多的吗?这个在线工具能减轻你配置Nginx的痛点。http://benhoad.net/hooli-sign…《硅谷》fans才能get到。有好事者创造了Hooli box signature edition。这样一本正经的幽默实在是无敌结束完成之后发现有图片会好很多。这次算了吧。

December 9, 2018 · 1 min · jiezi

服务器获取真实客户端 IP

服务器获取真实客户端 IP0x01 先查个问题测试环境微信支付通道提示网络环境未能通过安全验证,请稍后再试,出现这种情况一般首要 想到可能是双方网络交互中微信方验参与我们出现不一致,翻了下手册确定是这类问题开始排查环节可能获取真实IP方式错误getenv(‘HTTP_CLIENT_IP’)getenv(‘HTTP_X_FORWARDED_FOR’)getenv(‘REMOTE_ADDR’)filter_var($remote_ip, FILTER_VALIDATE_IP)已经依次获取并过滤固程序没有任何问题,往上发散是否反向代理经过反向代理后,由于在客户端和web服务器之间增加了中间层,因此web服务器无法直接拿到客户端的ip,只能通过$remote_addr变量拿到的将是反向代理服务器的ip地址,检查不存在此类问题,再往上,擅长网络工程的同学表示绝不认输可能NAT分配出口IP,或负载均衡服务分发出现异常先拿到我本地内网外网IP 方便之后问题排查# 本机IPifconfig | grep -A 1 “en” | grep broadcast | cut -d " " -f 2# 外网IPcurl –silent http://icanhazip.com检查与80端口建立连接目标都有谁 netstat -tn|grep 80|akw ‘{print $5}’|awk -F ‘{print $1}’ | grep [本地IP]这里出现问题,竟然没有我的IP,再以nginx $remote_addr拿到的IP作为参考,这是nginx最后一次握手的IP,$remote_addr = 10.168.0.0/16 段在nginx处打印$remote_addr,并在server_name添加当前机器ip,分别以负载均衡IP与本地IP做测试,最终确定问题出现在负载均衡服务器出现异常0x02 LNMP栈拿真实IPLNMP栈内PHP所有获得到的TCP操作信息都是由前面Nginx通过fastcgi传递给它的,就比如$_SERVER[‘REMOTE_ADDR’]由include fastcgi.conf;引进,其等于nginx的$remote_addrNginx中的几个变量:$remote_addr代表客户端的IP,但它的值不是由客户端提供的,而是服务端根据客户端的ip指定的,icanhazip的原理也是这样, 当你的浏览器访问某个网站时,假设中间没有任何代理,那么网站的web服务器就会把remote_addr设为你在公网暴露的IP,如果你用了某个代理,那么你的浏览器会先访问这个代理,然后再由这个代理转发到网站,这样web服务器就会把remote_addr设为这台代理机器的IP, 除非代理将你的IP附在请求header中一起转交给web服务器。$proxy_add_x_forwarded_for $proxy_add_x_forwarded_for变量包含客户端请求头中的"X-Forwarded-For",与$remote_addr两部分,他们之间用逗号分开。X-Forwarded-For(简称XFF),X-Forwarded-For 是一个 HTTP 扩展头部。RFC 2616 协议并没有对它的定义,它最开始是由 Squid 这个缓存代理软件引入,用来表示 HTTP 请求端真实 IP。如今它已经成为事实上的标准,被各大HTTP 代理、负载均衡等转发服务广泛使用,并被写入 RFC 7239(Forwarded HTTP Extension` 标准之中。$proxy_set_header已在排查问题中说明,可设置代理后 header proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;X-Real-IP 一般比如X-Real-IP这一个自定义头部字段,通常被 HTTP 代理用来表示与它产生TCP 连接的设备 IP,这个设备可能是其他代理,也可能是真正的请求端,这个要看经过代理的层级次数或是是否始终将真实IP一路传下来。(牢记:任何客户端传上来的东西都是不可信的)当多层代理或使用CDN时,如果代理服务器不把用户的真实IP传递下去,那么服务器将永远不可能获取到用户的真实IP。0x03 用户的真实IP从何而来宽带供应商提供独立IP 比如家里电信宽带上网,电信给分配了公网ip,那么一个请求经过的ip路径如下: 192.168.0.1 (用户) –> 192.168.0.1/116.1.2.3 (路由器的局域网ip及路由器得到的电信公网ip)–> 119.110.0.0/16 (负载均衡服务器)–> 10.168.0.0/32 (业务处理服务器)这种情况下,119.147.19.234 会把得到的116.1.2.3附加到头信息中传给10.168.0.0/32,因此这种情况下,我们取得的用户ip则为:116.1.2.3。如果119.110.0.0/16没有把116.1.2.3附加到头信息中传给业务服务器,业务服务器就只能取上上一级ip地址宽带供应商不能提供独立IP宽带提供商没有足够的公网ip,分配的是个内网ip,比如长宽等小的isp。请求路径则可能如下: 192.168.0.1 (用户) –> 192.168.0.1/10.0.1.2(路由器的局域网ip及路由器得到的运营商内网ip)–> 211.162.78.1 (网络运营商长城宽带的公网ip) 119.110.0.0/16 (负载均衡服务器)–> 10.168.0.0/32 (业务处理服务器)这种情况下得到的用户ip,就是211.162.78.1。 这种情况下,就可能出现一个ip对应有数十上百个用户的情况了手机2g上网网络提供商没法直接提供ip给单个用户终端,以中国移动cmwap上网为例,因此请求路径可能为: 手机(手机上没法查看到ip)–> 10.0.0.172(cmwap代理服务器ip)–> 10.0.1.2(移动运营商内网ip)–> 202.96.75.1(移动运营商的公网ip)–> 119.110.0.0/16 (负载均衡服务器)–> 10.168.0.0/32 (业务处理服务器)这种情况下得到的用户ip,就是202.96.75.1。2008年的时候整个广东联通就三个手机上网的公网ip,因此这种情况下,同一ip出现数十万用户也是正常的。有几万或数十万员工的公司,这种也会出现来自同一ip的超多用户,可能达到几万人,但出口IP可能就那么几个。0x04 NAT [Network Address Translation]中文意思是网络地址转换,它允许一个整体机构以一个公用IP地址出现在Internet上。NAT在 OSI参考模型的网络层 (第3层), 它是一种把内部私有网络地址(IP地址)翻译成合法网络IP地址的技术。NAT可以让那些使用私有地址的内部网络连接到Internet或其它IP网络上。NAT路由器在将内部网络的数据包发送到公用网络时,在IP包的报头把私有地址转换成合法的IP地址。RFC1918 规定了三块专有的地址,作为私有的内部组网使用A类:10.0.0.0 — 10.255.255.255 10.0.0.0/8B类:172.16.0.0 — 172.31.255.255 172.16.0.0/12C类:192.168.0.0 — 192.168.255.255 192.168.0.0/16这三块私有地址本身是可路由的,只是公网上的路由器不会转发这三块私有地址的流量;当一个公司内部配置了这些私有地址后,内部的计算机在和外网通信时,公司的边界路由会通过NAT或者PAT技术,将内部的私有地址转换成外网IP,外部看到的源地址是公司边界路由转换过的公网IP地址,这在某种意义上也增加了内部网络的安全性<center><img src="//i.qh13.cn/t/1544243390591.png" width=“348”/></center>这个过程是通过NAT中的本地址与全局地址映射条目来实现的,所以事先要在NAT路由器上配置这样的映射条目。<center><img src="//i.qh13.cn/t/1544243579906.png" width=“250”/></center>通过这种方式一个公网 IP 底下可以发私有的 IP 地址。0x05 IPV6 来了?<center><img src="//i.qh13.cn/t/1544241538381.png" width=“557”/></center>写这篇文章的时候看到有个推送,表示阿里全面应用IPV6,这件事的意义还挺重大的<center><img src="//i.qh13.cn/t/1544241839285.png" width=“265”/></center>我们知道,一段 IPv4 标准的 IP 地址,一共由 4 X 8 = 32 位二进制数字组成,理论上存在 2^32 个 IP 地址。等于 4,294,967,296 , 42 亿多个 IPv4 的地址。<center><img src="//i.qh13.cn/t/1544242178427.png" width=“751”/></center>参考世界互联网用户统计报告,全球现在大概有4,208,571,287人在上网,也就是说已经快到ipv4地址设计的最大IP数了不过不用担心,前面提到的 NAT,让 IPv4 公网 IP 哪怕用完了也能凑合过。<center><img src="//i.qh13.cn/t/1544243741020.png" width=“540”/></center>到了 IPv6 ,相比 IPv4 最大的提升,就是位数大大增加,变成了 8 个 4 位的十六进制数字。也就是说有 2^128个 IPv6 地址。地球上的每粒沙子都分一个也管够<center><img src="//i.qh13.cn/t/1544244141371.png" width=“300”/></center>存储2^128字节理论上什么概念呢,在当今的量子水平下,假设计算设备能够操作在原子一级,每公斤质量可存储大约10的25次方bits,存储2的128次方的字节大约需要272 trillion = 2720000亿公斤。最后,周末愉快,北京联通已经支持ipv6了,我在望京测试,可以拿到 ipv6地址 ...

December 8, 2018 · 1 min · jiezi

使用 Docker / Docker Compose 部署 Swoft 应用

Swoft首个基于 Swoole 原生协程的新时代 PHP 高性能协程全栈框架,内置协程网络服务器及常用的协程客户端,常驻内存,不依赖传统的 PHP-FPM,全异步非阻塞 IO 实现,以类似于同步客户端的写法实现异步客户端的使用,没有复杂的异步回调,没有繁琐的 yield, 有类似 Go 语言的协程、灵活的注解、强大的全局依赖注入容器、完善的服务治理、灵活强大的 AOP、标准的 PSR 规范实现等等,可以用于构建高性能的Web系统、API、中间件、基础服务等等。Swoft 的 Docker 镜像突然白话文 使用 docker 安装 swoft 其实听起来比较怪怪的,swoft 是一套 php 框架,依赖 swoole 扩展,说 docker 安装 swoft,其实是 docker 安装 swoft 运行所需的组件依赖和环境。swoft 框架运行环境所需的依赖和环境挨个安装搭建还是需要一些时间的,比如 php 版本 >= 7.1, swoole 版本 >= 2.1, 而且还要安装 hiredis 来协助开启 swoole 的异步 redis 客户端,同时要求 swoole 开启协程模式等。所以呢,为了节省我们的时间,官方提供了一个 docker 镜像包,里面包含了 swoft 运行环境所需要的各项组件:php 7.1+swoole 2.1+ –enable-async-redis-client –enable-coroutinecomposerpecl我们只需要下载镜像并新建一个容器,这个容器就提供了 swoft 框架所需的所有依赖和环境,我们只需要将本地的 sowft 项目挂载到镜像的 swoft 工作目录 /var/www/swoft 下,就可以继续我们的开发或生产工作了。让你从 swoft 略繁琐的依赖和环境搭建中解放出来,直接进入业务开发工作中去。一开始我没理解好这个 swoft 镜像,镜像里自带的框架其实是单纯的用来体验的,我一直误以为要编辑镜像的 swoft 框架源码做开发….需要特别注意的是,sowft 镜像的 entrypoint 命令(运行初始化命令)是ENTRYPOINT [“php”, “/var/www/swoft/bin/swoft”, “start”]即容器启动时会同时启动 swoft 框架,这就需要如果我们挂载本地 swoft 项目到镜像工作目录时,本地的 swoft 项目需已安装好框架的各依赖组件。我们从 git 上直接拉取的 swoft 项目是没有安装这些组件的,需使用 composer install [–no-dev] 安装,框架才能正常启动,这就要求宿主机上至少要有基础的 php + composer 的环境。当然,镜像内是工作目录下是有一套完全安装的 swoft 框架的,如果你只是为了体验,直接启动容器就好,但没什么直接的意义。在后面我们将给出一个只需要在宿主机上安装运维所需的 docker docker-compose git 即可完全借助 swoft 镜像去部署开发或生产环境的方法(修改镜像 entrypoint 到 bash 模式,然后进入镜像后使用 composer 安装依赖,启动 swoft)Docker 部署 swoft宿主机仍需安装基本的 php / composer(或者你把自己本地开发的项目cp过来,但这样可能会导致部分组件版本不一致,还是提交业务代码 + composer.json + composer.lock 文件,排除 vendor 目录,在线上服务器再 composer install 一遍最为规范)1、在宿主机创建 swoft 项目(宿主机需实安装基础的 php 环境来使用 composer)composer create-project –prefer-dist swoft/swoft swoft [–dev] && cd swoft或者git clone git@github.com:swoft-cloud/swoft.git && cd swoft && composer install && cd swoft2、拉取 swoft 镜像 创建 swoft 容器 并将宿主机上安装好的 swoft 项目挂载到 swoft 容器的工作目录// 拉取 swoft 镜像// 关联本地 swoft 项目目录到镜像的项目目录(/var/www/swoft)// 映射主机 8081 端口 到 容器 80 端口// 容器命名为 mySwoft// 守护模式启动docker run -v $(pwd):/var/www/swoft -p 8081:80 –name mySwoft -d swoft/swoft// 查看容器是否运行docker ps// 查看容器日志docker logs mySwoft3、进入 swoft 容器 shell// 交互模式执行 mySwoft 容器的 bashdocker exec -it mySwoft bash// stop 会停止容器所以会退出 shell 后用 docker start mySwoft 启动就好root@cce12db9add3:/var/www/swoft# php bin/swoft start|stop|reload// 因我们将宿主机上的swoft项目挂载到了swoft容器的项目目录/var/www/swoft 所以后期开发修改宿主机上的项目即可// 可以使用PS的FTP同步工具可以在 swoft 的容器 shell 里通过命令查看相应的组件版本root@cce12db9add3:/var/www/swoft# php -v root@cce12db9add3:/var/www/swoft# php –ri swooleroot@cce12db9add3:/var/www/swoft# composer -V root@cce12db9add3:/var/www/swoft# pecl -VDocker Composer 部署 Swoft宿主机仍需安装基本的 php / composer(或者你把自己本地开发的项目cp过来,但这样可能会导致部分组件版本不一致,还是提交业务代码 + composer.json + composer.lock 文件,排除 vendor 目录,在线上服务器再 composer install 一遍最为规范)swoft 项目中是有 docker-compose.yml 文件的version: ‘3’services: swoft: image: swoft/swoft:latest# build: ./ ports: - “80:80” #端口映射 volumes: - ./:/var/www/swoft # 挂载当前路径下的本地swoft项目到镜像项目路径 stdin_open: true #打开标准输出 tty: true # 打开 tty 会话 privileged: true # 给与权限 比如创建文件夹之类的 entrypoint: [“php”, “/var/www/swoft/bin/swoft”, “start”] # 入口启动命令 即启动 swoft 服务使用方法自然比直接用 docker 方便些,不过依旧是要在宿主机上先创建一个 swoft 项目1、在宿主机创建 swoft 项目(宿主机需实安装基础的 php 环境来使用 composer)composer create-project –prefer-dist swoft/swoft swoft [–dev] && cd swoft或者git clone git@github.com:swoft-cloud/swoft.git && cd swoft && composer install && cd swoft2、使用 docker-compose 来编排启动容器编辑 docker-compose.yaml 文件 给容器自定义个名字version: ‘3’services: swoft: image: swoft/swoft:latest container_name: mySwoft # 给容器自定义个名称便于管理# build: ./ ports: - “80:80” #端口映射 volumes: - ./:/var/www/swoft # 挂载当前路径下的本地swoft项目到镜像项目路径 stdin_open: true #打开标准输出 tty: true # 打开 tty 会话 privileged: true # 给与权限 比如创建文件夹之类的 entrypoint: [“php”, “/var/www/swoft/bin/swoft”, “start”] # 入口启动命令 即启动 swoft 服务# 启动容器docker-compose up -d swoft# 查看容器是否成功运行docker ps# 进入容器shelldocker exec -it mySwoft bash如何在未安装 PHP 环境的宿主机上部署 swoft前面两种部署 swoft 的方法都需要在宿主机上安装 php 基础环境来使用 composer 安装好本地 swoft 项目的依赖组件,才能与 swoft 镜像的工作目录挂载,启动容器(因为容器的入口命令就是直接启动 swoft,如果我们挂载本地未安装好依赖的 swoft 项目到镜像工作目录,那容器就会启动失败退出了),下面我们介绍一种不需要在宿主机上安装 php / composer 的方法。git+ docker + docker-composer1、拉取 swoft(拉取就好,不需要安装)git clone git@github.com:swoft-cloud/swoft.git && cd swoft && cd swoft2、编辑 docker-composer.yaml 文件修改容器启动的 entrypoint 命令,让容器启动,但不启动 swoft,等我们进入容器,使用容器中自带的 composer 安装好框架组件依赖后再启动 swoftversion: ‘3’services: swoft: container_name: mySwoft image: swoft/swoft:latest# build: ./ ports: - “8082:80” # 映射宿主机 8082 端口到 容器 80 volumes: - ./:/var/www/swoft stdin_open: true # 一定要开启此项 否则容器会因 bash 执行完退出 tty: true privileged: true# entrypoint: [“php”, “/var/www/swoft/bin/swoft”, “start”] entrypoint: [“bash”] # 改为此命令后 启动容器时默认不会启动 swoft 所以即使框架依赖未安装 也不会影响容器启动3、保存 docker-composer.yaml 后启动容器docker-composer up -d swoft4、进入容器 shell 使用容器种的 composer 安装框架依赖docker exec -it mySwoft bashcomposer install [–no-dev]5、启动 swoftphp bin/swoft start|stop|restart这种方法使得宿主机完全省去了还要事先简单安装下 php / composer 的工作,完全发挥镜像的功能~ ...

December 7, 2018 · 3 min · jiezi

Docker教程

这是今年国庆期间写的一个教程,一直没有充足的时间将之发布出来,慢慢的也变懒了,不想一篇一篇的排版,索性,打成pdf文档,一次性发布出来给各位小伙伴学习。 本教程一共21篇文章,每篇文章内容不多,目录如下:初识Docker容器基本操作-1容器基本操作-2容器内执行命令容器操作进阶容器导入和导出镜像介绍本地镜像管理创建本地镜像DockerfileDocker Hub自动化构建创建自己的Docker Hub容器网络数据卷入门宿主机目录做数据卷数据卷其他操作数据卷容器数据备份与恢复容器连接容器编排截图如下: 这个文档也算是自己的一个学习过程的记录吧,主要参考了《Docker开发实践》一书,感兴趣的读者也可以直接阅读原书。那么,如何获取这个教程呢?很简单,公众号后台回复 docker,即可获取pdf下载地址。 公众号二维码:好了,小伙伴们在学习过程中遇到什么问题,欢迎留言讨论。

December 6, 2018 · 1 min · jiezi

docker nginx 部署多个项目

前提条件1、本地电脑和服务器已安装 docker,下载方法自行谷歌吧2、在 docker hub 上已有账号, 注册传送门: https://hub.docker.com/3、需要对 docker 已有所熟悉 ,并了解Dockerfile里的一些指令使用Dockerfile 制作镜像假如本机有一个叫web的项目在web根目录下新建Dockerfile,写入以下内容FROM nginx:1.13.6-alpineLABEL maintainer=“lilywang <lilywang.cd@gmail.com>“ARG TZ=“Asia/Shanghai"ENV TZ ${TZ}RUN apk upgrade –update \ && apk add bash tzdata \ && ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime \ && echo ${TZ} > /etc/timezone \ && rm -rf /var/cache/apk/COPY dist /usr/share/nginx/html CMD [“nginx”, “-g”, “daemon off;"]此时web里的文件结构为:.|____Dockerfile|____dist // 为项目打包后的文件| |____index.html接下来在bash 进入到web目录cd webdocker build -t lilywang711/web .看到打印信息中有如下就说明镜像已经构建成功了Successfully built 4c050212ce0dSuccessfully tagged lilywang711/web:latest也可以输入docker images 查看当前的镜像列表接下来输入命令 docker push lilywang711/web 就可将刚才构建好的镜像上传到docker hub里面,方便等会儿我们在服务端拉取镜像如果是有多个项目需要部署,那就按照以上步骤重复来就行,有多少个项目就构建多少个镜像服务端部署ssh 登陆服务器,在当前用户目录下(我是root目录),新建 nginx 文件夹,并在里面新建nginx.conf在 nginx.conf 中写入以下内容user nginx;worker_processes 2;error_log /var/log/nginx/error.log warn;pid /var/run/nginx.pid;events { use epoll; worker_connections 2048;}http { include /etc/nginx/mime.types; # include /etc/nginx/conf.d/.conf; root /usr/share/nginx/html; index index.html index.htm; server { listen 80; server_name a.yourdomain.cn; location / { } } server { listen 80; server_name b.yourdomain.cn; location / { proxy_pass http://your_vps_ip:81; } } server { listen 80; server_name localhost; location / { } }}接下来启动docker systemctl start docker拉取刚才制作并上传好的两个镜像 docker pull lilywang711/web docker pull lilywang711/web1输入以下命令启动容器docker run -itd –name web -p 80:80 -v /root/nginx/nginx.conf:/etc/nginx/nginx.conf lilywang711/web// -i 交互模式运行容器, -t 为容器分配一个伪终端,-d 后台运行容器,可直接连写 -itd// –name 是给该容器起个叫web的名字,方便辨识// -p 是绑定端口 本机端口80:容器端口80// -v 声明volume,意思是将容器中的/etc/nginx/nginx.conf 挂载到 宿主机里的/root/nginx/nginx.conf,以后配置nginx只需要修改/root/nginx/nginx.conf就行了另外一个lilywang711/web1镜像也同理,修改下端口和名字就好了docker run -itd –name web1 -p 81:80 -v /root/nginx/nginx.conf:/etc/nginx/nginx.conf lilywang711/web1此时输入 docker ps 就可以看到这两个容器已经跑起来了docker化项目并在nginx部署就已经完成了在浏览器输入 http://a.yourdomain.cn 和 http://b.yourdomain.cn 就可以看到效果了,分别对应本地电脑中的web 和 web1 项目 ...

November 28, 2018 · 1 min · jiezi

Docker简介、常用命令与实践(二)

【上一篇:Docker简介、常用命令与实践(一)】六、Docker镜像操作6.1 获取镜像(下载镜像到本地)Docker Hub 上有大量的高质量的镜像可以用,这里我们就说一下怎么获取这些镜像。从 Docker 镜像仓库获取镜像的命令是 docker pull。其命令格式为:docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]docker中拉取指定版本的镜像比如:docker pull mongo:3.2.4dockre pull redis:3.2具体的选项可以通过 docker pull –help6.2 列出本地镜像要想列出已经下载下来的镜像,可以使用docker image ls或docker images命令。6.3 删除本地镜像如果要删除本地的镜像,可以使用 docker image rm 命令,其格式为:$ docker image rm [选项] <镜像1> [<镜像2> …]七、Docker容器操作7.1 说在前面/bin/bash是linux的命令行工具,类似于window的cmd进入dos系统。7.2 查看容器docker ps #查看启动的容器docker ps -a #查看所有容器,包含没有启动的容器7.3 启动容器 启动容器有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止状态(stopped)的容器重新启动。 因为 Docker 的容器实在太轻量级了,很多时候用户都是随时删除和新创建容器。 语法:docker run [OPTIONS] IMAGE [COMMAND] [ARG…]COMMAND: 容器启动后进入容器中要执行的命令.OPTIONS:(常用选项)-d: 让 Docker 在后台运行而不是直接把执行命令的结果输出在当前宿主机下-t: 选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上, -i: 则让容器的标准输入保持打开。-name:为容器命名-v: 创建数据卷-p: 绑定端口7.3.1 基于镜像新建一个容器并启动docker run 例如:docker run -p 5000:5000 –name web training/webapp7.3.2 后台运行 更多的时候,需要让 Docker 在后台运行而不是直接把执行命令的结果输出在当前宿主机下。此时,可以通过添加 -d 参数来实现。$ docker run -d CONTAINER [CONTAINER…]例如:docker run –d –p 5000:5000 training/webapp7.3.3 交互运行启动一个 bash 终端,允许用户进行交互。$ docker run -it CONTAINER [CONTAINER…] /bin/bash-t:选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上, -i: 则让容器的标准输入保持打开。在交互模式下,用户可以通过所创建的终端来输入linux命令例如:创建centos容器并且进入centos容器中docker run -it centos /bin/bash7.4 终止容器$ docker stop CONTAINER [CONTAINER…]例如:docker run –d –p 5000:5000 training/webapp7.5 启动一个停止的容器$ docker start CONTAINER [CONTAINER…]例如:docker stop web7.6 重启容器$ docker restart CONTAINER [CONTAINER…]例如:docker restart web 7.7 删除容器$ docker rm CONTAINER [CONTAINER…]例如:docker rm web 7.8 获取容器的输出信息$ docker logs CONTAINER [CONTAINER…]例如:docker logs web 7.9 进入容器$ docker exec –it CONTAINER /bin/bash例如:docker exec –it web 7.10 主机和容器间拷贝7.10.1.容器拷贝到主机docker cp ed0f8bb24f3e:/opt/webapp/app.py d:/wwwed0f8bb24f3e: 容器id/opt/webapp/app.py: 容器中的文件d:/www: 主机文件夹7.10.2.主机拷贝到容器docker cp d:/www/文件 ed0f8bb24f3e:/opt/webapp/ 八、数据卷8.1 什么是数据卷数据卷 是一个可供一个或多个容器使用的特殊目录,可以绕过联合文件系统(UFS),为一个或多个容器提供访问。数据卷设计的目的在于对数据的持久化,它完全独立于容器的生命周期,因此Docker不会在删除容器时删除其挂载的数据卷,也不会存在类似垃圾收集的机制。数据卷提供很多有用的特性:数据卷 可以在容器之间共享和重用对数据卷的修改会立马生效对数据卷的更新,不会影响镜像数据卷默认会一直存在,即使容器被删除注意:数据卷的使用,类似于Linux下对目录或文件进行mount,镜像中的被指定为挂载点的目录中的文件会隐藏掉,能显示看的是挂载的数据卷。8.2 为什么要使用数据卷将本地主机中的代码运行在docker容器中。8.3 挂载数据卷的前提授权本地磁盘8.4 挂载一个主机目录作为数据卷docker run -d -v d:/www/webapp:/opt/webapp training/webapp 九、常见疑问Docker的容器是以镜像来创建的,镜像是不是一个类似操作系统的环境?是的,你可以将Docker理解为一个轻量化的虚拟机,至于我们经常说的什么Tomcat镜像、MySQL镜像之类的,只是这个轻量化的虚拟机中安装了相应的软件。镜像的名字也就说明了镜像的功能。是不是到正式环境上还要安装Docker,然后把应用部署在其中?是的,构建Docker镜像、运行Docker容器,都需要安装Docker,但这是一劳永逸的,因为我们使用的是安装了各种各样功能软件的Docker镜像和Docker容器。参考教程:Docker之编程环境应用 ...

November 26, 2018 · 1 min · jiezi

Spring Cloud底层原理

毫无疑问,Spring Cloud 是目前微服务架构领域的翘楚,无数的书籍博客都在讲解这个技术。不过大多数讲解还停留在对 Spring Cloud 功能使用的层面,其底层的很多原理,很多人可能并不知晓。实际上,Spring Cloud 是一个全家桶式的技术栈,它包含了很多组件。本文先从最核心的几个组件,也就是 Eureka、Ribbon、Feign、Hystrix、Zuul 入手,来剖析其底层的工作原理。业务场景介绍先来给大家说一个业务场景,假设咱们现在开发一个电商网站,要实现支付订单的功能。流程如下:创建一个订单后,如果用户立刻支付了这个订单,我们需要将订单状态更新为“已支付”。扣减相应的商品库存。通知仓储中心,进行发货。给用户的这次购物增加相应的积分。针对上述流程,我们需要有订单服务、库存服务、仓储服务、积分服务。整个流程的大体思路如下:用户针对一个订单完成支付之后,就会去找订单服务,更新订单状态。订单服务调用库存服务,完成相应功能。订单服务调用仓储服务,完成相应功能。订单服务调用积分服务,完成相应功能。至此,整个支付订单的业务流程结束。下面这张图,清晰表明了各服务间的调用过程:好!有了业务场景之后,咱们就一起来看看 Spring Cloud 微服务架构中,这几个组件如何相互协作,各自发挥的作用以及其背后的原理。Spring Cloud 核心组件:Eureka咱们来考虑第一个问题:订单服务想要调用库存服务、仓储服务,或者积分服务,怎么调用?订单服务压根儿就不知道人家库存服务在哪台机器上啊!它就算想要发起一个请求,都不知道发送给谁,有心无力!这时候,就轮到 Spring Cloud Eureka 出场了。Eureka 是微服务架构中的注册中心,专门负责服务的注册与发现。咱们来看看下面的这张图,结合图来仔细剖析一下整个流程:如上图所示,库存服务、仓储服务、积分服务中都有一个 Eureka Client 组件,这个组件专门负责将这个服务的信息注册到 Eureka Server 中。说白了,就是告诉 Eureka Server,自己在哪台机器上,监听着哪个端口。而 Eureka Server 是一个注册中心,里面有一个注册表,保存了各服务所在的机器和端口号。订单服务里也有一个 Eureka Client 组件,这个 Eureka Client 组件会找 Eureka Server 问一下:库存服务在哪台机器啊?监听着哪个端口啊?仓储服务呢?积分服务呢?然后就可以把这些相关信息从 Eureka Server 的注册表中拉取到自己的本地缓存中来。这时如果订单服务想要调用库存服务,不就可以找自己本地的 Eureka Client 问一下库存服务在哪台机器?监听哪个端口吗?收到响应后,紧接着就可以发送一个请求过去,调用库存服务扣减库存的那个接口!同理,如果订单服务要调用仓储服务、积分服务,也是如法炮制。总结一下:Eureka Client:负责将这个服务的信息注册到 Eureka Server 中。Eureka Server:注册中心,里面有一个注册表,保存了各个服务所在的机器和端口号。Spring Cloud 核心组件:Feign现在订单服务确实知道库存服务、积分服务、仓库服务在哪里了,同时也监听着哪些端口号了。但是新问题又来了:难道订单服务要自己写一大堆代码,跟其他服务建立网络连接,然后构造一个复杂的请求,接着发送请求过去,最后对返回的响应结果再写一大堆代码来处理吗?这是上述流程翻译的代码片段,咱们一起来看看,体会一下这种绝望而无助的感受!!!友情提示,前方高能:看完上面那一大段代码,有没有感到后背发凉、一身冷汗?实际上你进行服务间调用时,如果每次都手写代码,代码量比上面那段要多至少几倍,所以这个事压根儿就不是地球人能干的。既然如此,那怎么办呢?别急,Feign 早已为我们提供好了优雅的解决方案。来看看如果用 Feign 的话,你的订单服务调用库存服务的代码会变成啥样?看完上面的代码什么感觉?是不是感觉整个世界都干净了,又找到了活下去的勇气!没有底层的建立连接、构造请求、解析响应的代码,直接就是用注解定义一个 Feign Client 接口,然后调用那个接口就可以了。人家 Feign Client 会在底层根据你的注解,跟你指定的服务建立连接、构造请求、发起请求、获取响应、解析响应,等等。这一系列脏活累活,人家 Feign 全给你干了。那么问题来了,Feign 是如何做到这么神奇的呢?很简单,Feign 的一个关键机制就是使用了动态代理。咱们一起来看看上面的图,结合图来分析:首先,如果你对某个接口定义了 @FeignClient 注解,Feign 就会针对这个接口创建一个动态代理。接着你要是调用那个接口,本质就是会调用 Feign 创建的动态代理,这是核心中的核心。Feign的动态代理会根据你在接口上的 @RequestMapping 等注解,来动态构造出你要请求的服务的地址。最后针对这个地址,发起请求、解析响应。Spring Cloud 核心组件:Ribbon说完了 Feign,还没完。现在新的问题又来了,如果人家库存服务部署在了 5 台机器上。如下所示:192.168.169:9000192.168.170:9000192.168.171:9000192.168.172:9000192.168.173:9000这下麻烦了!人家 Feign 怎么知道该请求哪台机器呢?这时 Spring Cloud Ribbon 就派上用场了。Ribbon 就是专门解决这个问题的。它的作用是负载均衡,会帮你在每次请求时选择一台机器,均匀的把请求分发到各个机器上。Ribbon 的负载均衡默认使用的最经典的 Round Robin 轮询算法。这是啥?简单来说,就是如果订单服务对库存服务发起 10 次请求,那就先让你请求第 1 台机器、然后是第 2 台机器、第 3 台机器、第 4 台机器、第 5 台机器,接着再来—个循环,第 1 台机器、第 2 台机器。。。以此类推。此外,Ribbon 是和 Feign 以及 Eureka 紧密协作,完成工作的,具体如下:首先 Ribbon 会从 Eureka Client 里获取到对应的服务注册表,也就知道了所有的服务都部署在了哪些机器上,在监听哪些端口号。然后 Ribbon 就可以使用默认的 Round Robin 算法,从中选择一台机器。Feign 就会针对这台机器,构造并发起请求。对上述整个过程,再来一张图,帮助大家更深刻的理解:Spring Cloud 核心组件:Hystrix在微服务架构里,一个系统会有很多的服务。以本文的业务场景为例:订单服务在一个业务流程里需要调用三个服务。现在假设订单服务自己最多只有 100 个线程可以处理请求,然后呢,积分服务不幸的挂了,每次订单服务调用积分服务的时候,都会卡住几秒钟,然后抛出—个超时异常。咱们一起来分析一下,这样会导致什么问题?如果系统处于高并发的场景下,大量请求涌过来的时候,订单服务的 100 个线程都会卡在请求积分服务这块,导致订单服务没有一个线程可以处理请求。然后就会导致别人请求订单服务的时候,发现订单服务也挂了,不响应任何请求了。上面这个,就是微服务架构中恐怖的服务雪崩问题,如下图所示:如上图,这么多服务互相调用,要是不做任何保护的话,某一个服务挂了,就会引起连锁反应,导致别的服务也挂。比如积分服务挂了,会导致订单服务的线程全部卡在请求积分服务这里,没有一个线程可以工作,瞬间导致订单服务也挂了,别人请求订单服务全部会卡住,无法响应。但是我们思考一下,就算积分服务挂了,订单服务也可以不用挂啊!为什么?我们结合业务来看:支付订单的时候,只要把库存扣减了,然后通知仓库发货就 OK 了。如果积分服务挂了,大不了等它恢复之后,慢慢人肉手工恢复数据!为啥一定要因为一个积分服务挂了,就直接导致订单服务也挂了呢?不可以接受!现在问题分析完了,如何解决?这时就轮到 Hystrix 闪亮登场了。Hystrix 是隔离、熔断以及降级的一个框架。啥意思呢?说白了,Hystrix 会搞很多个小小的线程池,比如订单服务请求库存服务是一个线程池,请求仓储服务是一个线程池,请求积分服务是一个线程池。每个线程池里的线程就仅仅用于请求那个服务。打个比方:现在很不幸,积分服务挂了,会咋样?当然会导致订单服务里那个用来调用积分服务的线程都卡死不能工作了啊!但由于订单服务调用库存服务、仓储服务的这两个线程池都是正常工作的,所以这两个服务不会受到任何影响。这个时候如果别人请求订单服务,订单服务还是可以正常调用库存服务扣减库存,调用仓储服务通知发货。只不过调用积分服务的时候,每次都会报错。但是如果积分服务都挂了,每次调用都要去卡住几秒钟干啥呢?有意义吗?当然没有!所以我们直接对积分服务熔断不就得了,比如在 5 分钟内请求积分服务直接就返回了,不要去走网络请求卡住几秒钟,这个过程,就是所谓的熔断!那人家又说,兄弟,积分服务挂了你就熔断,好歹你干点儿什么啊!别啥都不干就直接返回啊?没问题,咱们就来个降级:每次调用积分服务,你就在数据库里记录一条消息,说给某某用户增加了多少积分,因为积分服务挂了,导致没增加成功!这样等积分服务恢复了,你可以根据这些记录手工加一下积分。这个过程,就是所谓的降级。为帮助大家更直观的理解,接下来用一张图,梳理一下 Hystrix 隔离、熔断和降级的全流程:Spring Cloud 核心组件:Zuul说完了 Hystrix,接着给大家说说最后一个组件:Zuul,也就是微服务网关。这个组件是负责网络路由的。不懂网络路由?行,那我给你说说,如果没有 Zuul 的日常工作会怎样?假设你后台部署了几百个服务,现在有个前端兄弟,人家请求是直接从浏览器那儿发过来的。打个比方:人家要请求一下库存服务,你难道还让人家记着这服务的名字叫做 inventory-service?部署在 5 台机器上?就算人家肯记住这一个,你后台可有几百个服务的名称和地址呢?难不成人家请求一个,就得记住一个?你要这样玩儿,那真是友谊的小船,说翻就翻!上面这种情况,压根儿是不现实的。所以一般微服务架构中都必然会设计一个网关在里面。像 Android、iOS、PC 前端、微信小程序、H5 等等,不用去关心后端有几百个服务,就知道有一个网关,所有请求都往网关走,网关会根据请求中的一些特征,将请求转发给后端的各个服务。而且有一个网关之后,还有很多好处,比如可以做统一的降级、限流、认证授权、安全,等等。如果想免费学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java进阶群:478030634,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。总结最后再来总结一下,上述几个 Spring Cloud 核心组件,在微服务架构中,分别扮演的角色:Eureka:各个服务启动时,Eureka Client 都会将服务注册到 Eureka Server,并且 Eureka Client 还可以反过来从 Eureka Server 拉取注册表,从而知道其他服务在哪里。Ribbon:服务间发起请求的时候,基于 Ribbon 做负载均衡,从一个服务的多台机器中选择一台。Feign:基于 Feign 的动态代理机制,根据注解和选择的机器,拼接请求 URL 地址,发起请求。Hystrix:发起请求是通过 Hystrix 的线程池来走的,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题。Zuul:如果前端、移动端要调用后端系统,统一从 Zuul 网关进入,由 Zuul 网关转发请求给对应的服务。以上就是我们通过一个电商业务场景,阐述了 Spring Cloud 微服务架构几个核心组件的底层原理。文字总结还不够直观?没问题!我们将 Spring Cloud 的 5 个核心组件通过一张图串联起来,再来直观的感受一下其底层的架构原理: ...

November 22, 2018 · 1 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

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

本文根据美团基础架构部/容器研发中心技术总监欧阳坚在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

集成spring boot + mysql + docker实战

前言网上找过很多文章,关于通过docker构建mysql容器并将应用容器和docker容器关联起来的文章不多。本文将给出具体的范例。此处为项目的源码前置条件该教程要求在宿主机上配置了:dockermavenmysql容器新建一个mysql容器和别的教程没什么区别,这里我们将直接利用官方镜像来启动一个空的mysql容器。完整的内容位于mysql目录之下。只需要直接执行脚本sh start_mysql.sh即可启动一个包含位于container_demo数据库中的user表的数据库。使用语句docker exec -it demo_db mysql -u root -p可以进入容器中的mysql进程并查看我们的初始化情况。spring mvc之后就是初始化一个springmvc项目,同样的源码为src目录下,可以在github上看到。首先使用docker ps查看本地启动的mysql的端口号,并且修改application-dev.yml中的数据库信息。此时可以直接在idea总启动项目。比如这里我看到本地的端口号为32809,所以可以通过32809这个端口号直接访问数据库。在docker中使用的是test环境的配置,所以docker中的配置都应该写在test中。对源码在使用中的问题,欢迎留言或者提issue参考文章Spring Boot with Docker docker指令学习记录customize mysql dockerdocker安装mysql

November 15, 2018 · 1 min · jiezi

给前端工程插上Docker的翅膀

What Docker/Why Docker/Install Docker,略没错,作者很懒…,总而言之,你需要Docker来编译前端工程,也需要Docker来启动静态资源服务器,接下来就和大家一起来实践一下。Step 1:启动Nginx容器执行docker pull nginx,即可从远程仓库中拉取标记为latest(一般为最近更新版本)的nginx镜像,此时可通过docker images查看本地镜像:$ docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEnginx latest dbfc48660aeb 2 weeks ago 109MB首先通过docker run命令结合一些参数来跑一个容器服务吧:docker run -d –rm -p 3333:80 –name nginx-demo nginx,访问http://localhost:3333:Welcome to nginx!If you see this page, the nginx web server is successfully installed and working. Further configuration is required.For online documentation and support please refer to nginx.org.Commercial support is available at nginx.com.Thank you for using nginx.当执行run的时候,有几个参数需要说明一下:–name,命名容器id-d,后台运行此容器–rm,退出时自动删除容器-p,将容器的端口发布到主机的端口,3333:80代表将容器的80端口映射到主机的3333端口没错,启动一个Nginx服务就是那么简单。Step 2:使用Nginx容器为了使用Nginx,我们需要做两件事:更新Nginx中的静态资源文件自定义Nginx配置文件我们先进入到容器的shell环境查看一下基本的配置信息:$ docker exec -it nginx-demo sh其中,docker exec用于在运行的容器中执行命令,还使用了一些参数:-i,保持STDIN打开-t,分配一个TTY终端进入到系统中以后,很容易找到路径为/etc/nginx/conf.d/default.conf的文件:# 已去掉文件注释server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; }}这样我们就能知道Nginx的服务路径了,之后我们可以采用挂载的方式,将本机的文件路径挂载到容器内对应路径,这样通过修改主机的文件,Nginx也会进行相应的更新。为了验证是否配置成功,首先我们来创建一个~/nginx-demo文件夹与~/nginx-demo/index.html文件:$ cd ~ && mkdir nignx-demo && cd nginx-demo && echo ‘<h1>Hello World</h1>’ > index.html然后停掉之前的容器,再重新启动,并附上挂载路径:$ docker stop nginx-demo$ docker run -d –rm -p 3333:80 \ -v /nginx-demo:/usr/share/nginx/html \ –name nginx-demo nginx$ curl http://localhost:3333<h1>Hello World</h1>-v参数指将主机目录挂载到容器目录,除此以外,我们还想同时自定义/etc/nginx/conf.d/default.conf,通过docker cp将它拷贝到/docker-nginx/进行修改:$ docker cp nginx-demo:/etc/nginx/conf.d/default.conf ~/docker-nginx/default.conf在这里仅仅为了测试,因此将location改为/hello-world/,期望当访问http://localhost:3333/hello-world/也能返回正确的结果,修改后的配置文件为:server { listen 80; server_name localhost; root /usr/share/nginx/html; location /hello-world/ { alias /usr/share/nginx/html/; index index.html; }}现在再需要重新构建容器,并将Nginx配置文件也从主机挂载到容器中:$ docker stop nginx-demo$ docker run -d –rm -p 3333:80 \ -v ~/nginx-demo:/usr/share/nginx/html \ -v ~/nginx-demo/default.conf:/etc/nginx/conf.d/default.conf \ –name nginx-demo nginx$ curl -L http://localhost:3333/hello-world/<h1>Hello World</h1>如果你和我一样,感觉每次执行 docker run 携带一大堆参数不太优雅,那就试试docker-compose吧,它是一种YAML配置文件,主要的职责是完成容器的编排,当然也可以为容器的运行配置一些参数,为了实现相同的效果,于是乎得到了下面的docker-compose.yml文件:services: web: image: nginx container_name: nginx-demo ports: - 3333:80 volumes: - ~/nginx-demo:/usr/share/nginx/html - ~/nginx-demo/default.conf:/etc/nginx/conf.d/default.confdocker-compose还有一些常用命令:docker-compose up -d,创建并启动容器docker-compose down,停止并删除容器docker-compose –help,查看帮助相信你可以通过上述命令让你的容器飞起来。Step 3:使用Dockerfile构建工程镜像在这里就直接使用create-react-app先创建一个前端工程:$ npx create-react-app react-app进入到项目,执行yarn build,即可在根目录生成build目录。我们首先采取以下步骤:编译前端项目。将编译后的文件拷贝到Nginx镜像,如有需要你也可以创建一个nginx文件,拷贝到镜像中替换掉默认配置。因此,我们写一个最简单的Dockerfile:FROM nginxCOPY nginx.conf /etc/nginx/conf.d/default.confCOPY build/ /usr/share/nginx/htmlEXPOSE 80相信不用解释大家也明白指令的意义,在这里用COPY代替了之前的挂载文件夹的方式,我们现在就在跑一下这个容器吧:$ docker build –tag react-app .$ docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEreact-app latest f151ab2c873e 6 seconds ago 110MB$ docker run –rm -p 3333:80 react-app此时访问http://localhost:3333 ,恭喜你运行成功。在通常构建流程中,我们也会将编译所需环境与编译脚本集成进构建镜像步骤中,因此上述Dockerfile可改写为:# stage 1FROM node:11-alpine as build-depsWORKDIR /usr/src/appCOPY . .RUN yarn && yarn build# stage 2FROM nginxCOPY build/ /usr/share/nginx/htmlEXPOSE 80node的镜像版本号一定要采用-alpine后缀的,这样构建的镜像会节约很多空间,因为它是基于 Alpine Linux项目,它是一个轻量级Linux发行版本。当我们重新构建完镜像后,自行验证一下吧。需要注意的是,当你在构建时无意中会把node_modules、.git之类的文件夹一起放到构建上下文之中,这样会影响到构建速度与镜像大小,这时候.dockerignore文件就排上用场啦,它能帮助你去定义构建环境中你真正需要的文件,在这里也为工程简单配置了一下:node_modules.git参考资料How To Run Nginx in a Docker Container on Ubuntu 14.04 | DigitalOceanCreate React App + Docker — multi-stage build example. Let’s talk about artifacts!Do not ignore .dockerignore (it’s expensive and potentially dangerous) ...

November 14, 2018 · 2 min · jiezi

k8s 1.12.1 的坑和解决

k8s 1.12.1 的坑和解决pull 镜像:gcr.io 被墙,需要 pull 自己的镜像,然后改 tag。具体需要 pull 哪些镜像呢,kubeadm config images 可查看提示没权限:kubeadm reset 重复安装的时候,.kube 文件夹不会清空,但 key 已经重新生成了,所有会key secret 不匹配。解决办法是清空 .kube 目录,然后将 /etc/kubernetes/kube-admin.json 拷贝过来corednspending,network not ready:安装对应版本的 flannel。kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml1 node(s) had taints that the pod didn’t tolerate:默认 k8s 不允许往 master 节点装东西,强行设置下允许:kubectl taint nodes –all node-role.kubernetes.io/master-azure xxx:kubelet 自己的 bug, 无视The connection to the server localhost:8080 was refused :sudo cp /etc/kubernetes/admin.conf $HOME/sudo chown $(id -u):$(id -g) $HOME/admin.confexport KUBECONFIG=$HOME/admin.confhelm 没权限That’s because you don’t have permission to deploy tiller, add an account for it:kubectl create serviceaccount –namespace kube-system tiller serviceaccount “tiller” createdkubectl create clusterrolebinding tiller-cluster-rule –clusterrole=cluster-admin –serviceaccount=kube-system:tiller clusterrolebinding “tiller-cluster-rule” createdkubectl patch deploy –namespace kube-system tiller-deploy -p ‘{“spec”:{“template”:{“spec”:{“serviceAccount”:“tiller”}}}}’ deployment “tiller-deploy” patchedThen run below to check it :helm listhelm repo update ...

November 14, 2018 · 1 min · jiezi

Golang 在十二赞的深度应用

Golang 在十二赞的深度应用我们是“十二赞”,一个致力于帮助电商卖家进入小程序的小团队,我们的主页是http://www.12zan.cn/。在实际运行中,我们使用了大量由golang写就的小工具,几乎每一个工具代码量都超短,一般在200行左右就完成了一个独立的功能,同时担当了相当重要的角色;像代理服务器,代码量一共500行多一点点,却是我们的核心支柱,压测时QPS也直追nginx,表现优异。基于Docker的基础结构做为基础架构,我介绍一下我们的机器架构。我们的整个业务构建于阿里云之上,有5台server,每一对都有独立的外网IP,同时也在同一个内网之中。在每一台机器上都跑了一个我们自己用golang写的守护进程,这个进程负责监听一些业务重启、新增域名等类似的指令并执行(这些指令最后都传递给了docker)。同时,每台机器上都有一个consul进程,这些consul都join到了一起。另外,我们每一台机器上,都用docker跑了一个nginx来做80端口的服务,同时跑了一个用golang自己写的HTTP代理。nginx做做日志啊基础的功能之后就把请求丢给这个http代理 ,HTTP代理会到consul里去查应该将请求转发到哪个IP的哪个端口上。400行Golang代码写的HTTP Proxy在架构选型的第一天,我们就决定,我们会服务化,会大量使用http 接口来提供服务,并使用自己的http proxy来分发请求、添加自有的一些业务逻辑比如API的权限验证等逻辑。我们限定,所有业务,域名都是*.app.12zan.net,比如我们要上一个聊天服务,请求的接口就会是chat.app.12zan.net,哪天再上个评论服务,请求的接口就是comment.app.12zan.net。确定域名后我第一件事情,就是拿golang自己写了一个非常简单的基于consul的http proxy server;感谢Golang这完善的HTTP库,我们只用了几百行代码就完成了所有功能。每当有http请求过来时,这个proxy server就会根据HTTP请求中HTTP_HOST 字段去consul去查,有哪些后端是用这个域名名称来注册服务的,并根据指定的算法,取出一台后端来,把这个HTTP请求Proxy过去。每个具体的业务,可能运行在我们5台机器中的任何一台之中的docker上,也可能是多个docker实例上。所以这里有一个机制,选择哪个实际的docker实例来服务这个请求的问题。我们现在支持随机选取、按客户端IP地址做hash之后选取、按URL做Hash选取、按负载选取几种方式。WEB服务的服务注册基于php+laralel和nodejs+koajs两种场景,我们制作了自己的docker镜像。这个镜像除开可以将php+nginx和nodejs构建的web服务运行起来之外,还包含一个golang写的consul客户端。在docker容器里,这个客户端随着php+nginx或是nodejs的web服务一起启动,启动之后会向宿主机的consul 进程注册自己这个服务,注册的时候会通知说,某某应用,在某某IP某某端口提供服务啦,如果前面有到**.app.12zan.net的请求你可以转发给我;同时会每隔一秒上报自己的进程数、当前机器CPU占用、内存占用情况。也是一样的简单,几百行golang代码,就鼓捣出了这个consul客户端。为什么使用golang呢?第一个原因当然是因为consul天生是golang阵营,第二个,是因为我们的docker容器种类较多,所以这个客户端直接就是在Mac上跨平台编译出来的在linux64平台上运行的,不管docker容器是python为基准的还是ruby为基准的,还是nodejs的,只要把这个二进制文件拷贝进去就能正确运行,不像别的语言需要解决依赖问题。我们还开发了一个web console界面,在这里,我们可以注册app,也可以为app新增实例。注册app时,我们要指定代码仓库的地址(对了,我们的代码管理是用的golang写的gogs),指定对外服务的域名,指定是nodejs应用还是php+laravel应用。添加应用之后,可以在这个应用下新建实例,让系统在指定的IP上去跑这个实例。实例运行的过程实际就是下发一个通知到某个机器上,去执行一个docker实例启动的过程。docker启动的时候带了一些环境变量,比如当前内网IP、docker监听的端口、对外提供服务时是用何域名提供服务。日志和存储前面这种架构有一个问题,就是后端可能是在任何一台机器上运行的,今天可能是A,明天可能是B,那我要是把文件存在A上了是不是让B来提供服务的时候就挂掉了?所以我们想了这么一个办法(也是因为穷。。。。),我们把所有的文件都挪到阿里云的OSS服务上。同时为了不管是Nodejs应用还是php应用 还是python写的应用都能做到把用户上传的文件或是系统生成的文件存到oss上面,我们很省事地写了一个ossUploader,编译好的可执行文件发布,只需要执行它,传进来本地路径和oss上的目标路径,就保证给你上传到oss上去就完整,不需要再在nodejs、php、python、ruby、java各种平台下都琢磨一遍oss的SDK。对日志的处理是一样的, 所有的日志文件的内容,都会被一个golang写的工具gtail监听着(就像linux 的tail -f命令一样),所有新产生的内容都会被gtail挪到oss上去存储。当然,也是可执行文件发布的。这个实现之后, 我们的实际业务就真正可以在5台机器上之间任意腾挪了。消息广播得益于golang的一些开源仓库,我们还做了一些好玩的东西。比如,看到https://github.com/gorilla/we…,我们忍不住撸了一个websocket server,或是说叫群聊服务器更好一点。接下来,我们看到有一个golang的库叫go-mysql-elasticsearch,伪装了一个mysql的slave,去MySQL的master机器上去读binlog,读到binlog以后就将MySQL里的数据发送给ElasticSearch去索引数据。我们就结合了一个,把这两个结合起来,修改了一下go-mysql-elastichsearch,让它监听到MySQL的数据变更之后,在WebSocket server的某个群聊里推送出来,形成一个数据变更的广播。再接下来我们就可以用nodejs写一个应用,连上这个websocket server,加入特定的某个群聊,就源源不断地收听到数据变更的消息。这个nodejs端的代码就非常简洁了,只需要不到100行代码可以做各种好玩的事情,比如监听到用户留言表有新增,可以发邮件让运营马上去审核。还有比如说,每当订单表有成交的时候,我们某个小小的nodejs应用因为监听了数据库消息,第一时间就知道了,马上就去追溯用户来源,来计算返利;同时这个nodejs的代码更新是和订单主逻辑完全不相关的,写这个业务的开发人员只需要知道订单表的结构,不需要了解订单应用后台代码。【原文链接】

November 14, 2018 · 1 min · jiezi

Liunx docker-compose 实战

Docker是一个开源的引擎,可以轻松的为任何应用创建一个轻量级的、可移植的、自给自足的容器。开发者在笔记本上编译测试通过的容器可以批量地在生产环境中部署,包括VMs(虚拟机)、bare metal、OpenStack 集群和其他的基础应用平台。容器技术是继大数据和云计算之后又一炙手可热的技术,而且未来相当一段时间内都会非常流行。本文介绍在Liunx下docker-compose编排PHP基本环境基本步骤,废话少说直奔主题。安装docker本文选用 Centos7.3系统确保系统中无残留dockersudo yum remove docker \docker-common \docker-selinux \docker-engine-selinux \docker-engine \docker-ce安装前准备sudo yum install -y yum-utils device-mapper-persistent-data lvm2更换yum 软件源sudo yum-config-manager \ –add-repo \ https://download.docker.com/linux/centos/docker-ce.repo设置使用最新Docker CEsudo yum-config-manager –enable docker-ce-edgesudo yum-config-manager –enable docker-ce-test安装dockersudo yum install docker-ce如果非root用户配置非root运行dockersudo usermod -aG docker 用户名启动service docker start查看是否安装成功docker info配置加速https://www.daocloud.io/ (加速器申请地址#回到服务器将你或得到的命令直接运行curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://你的编号.m.daocloud.io#重启dockerservice docker restart安装docker-composehttps://github.com/docker/com… github下载地址执行conpose下载curl -L https://github.com/docker/compose/releases/download/1.23.1/docker-compose-`uname -s-uname -m` -o /usr/local/bin/docker-composechmod +x /usr/local/bin/docker-compose实验docker-compose -version环境搭建成功后可观看 docker编排PHP开发坏境 部署属于你的环境致谢谢谢各位观看,欢迎讨论~

November 2, 2018 · 1 min · jiezi

alinode 新手村生存指南

原文博客: http://www.52cik.com/2018/10/...alinode 是阿里云产品 Node.js 性能平台 的前身。以前是收费项目,2018年初集成到阿里云后就完全免费了,我也是第一时间接入测试并上线使用。在群里安利了不少小伙伴,但真正使用的不是很多,因为大部分都不知道 node 是怎么跑起来的。说多了都是泪,所以打算写个简单教程,让小白也可以分分钟上手 alinode 这种神兵利器。基础知识先来了解下 alinode 是个啥东西吧,这里我找了些资料。有朴灵大大在 阿里D2前端技术论坛——2015融合 上的分享 alinode与Node应用性能管理(1)、alinode与Node应用性能管理(2)。还有 Node.js 性能平台官方文档 以及 云栖社区 Node.js 性能平台官方博客。好了,文档比较多,如果你懒的看,那我简单一句话帮你概括下。alinode 就是朴灵大大修改了 node 源码,可以将 cpu、内存、GC 等数据导出的 node 版本。目前没有开源,不过他们准备尽快开源。由于没有开源,所以一些小伙伴不放心,生怕有后门,泄露公司机密。其实完全没必要担心,这种事情简单抓包就可以看出有没有后门了。而且我也有幸在机缘巧合下看到了 alindoe 源码,对比了官方源码后可以很负责任的告诉你完全没有后门。如何使用如果你会在线上安装 node 并且启动你的应用,那么你就会使用。原理是一样的,剩下的,不管你怎么启动项目,直接启动,用 pm2 或者其他方法,都无所谓。安装使用 (tnvm 版本)官网推荐使用 tnvm 安装,就是 nvm 的淘宝镜像版本,并且加入了 alinode 而已。# 安装版本管理工具 tnvm,安装过程出错参考:https://github.com/aliyun-node/tnvmwget -O- https://raw.githubusercontent.com/aliyun-node/tnvm/master/install.sh | bashsource ~/.bashrc # 加载配置,使当前环境立即生效tnvm ls-remote alinode # 查看需要的版本tnvm install alinode-v3.12.0 # 安装需要的版本tnvm use alinode-v3.12.0 # 使用需要的版本npm install @alicloud/agenthub -g # 安装 agenthub安装好 alinode 后,还安装了一个 @alicloud/agenthub 模块。官网介绍:agenthub 是由 Node.js 性能平台提供的 agent 命令程序,用于协助您的 Node 应用性能数据上报和问题诊断。简单说就是将 alinode 生成的 cpu 内存 GC 等数据上传到 Node.js 性能平台 上,这样我们就可以在平台上看到监控数据了。好了到此安装完成,然后就是创建应用和使用了。安装使用 (docker 版本)官网提供了 docker 镜像 docker pull registry.cn-hangzhou.aliyuncs.com/aliyun-node/alinode。但也一直被吐槽太TM大了,都是些啥啊,咋就这么大,,其实官网 node 镜像也一样大。但在9月份的时候,他们提供了 alpine 版本,截止目前才 79.5MB 大小,非常接近官网 node 的 alpine 版本镜像。快来试试吧 docker pull registry.cn-hangzhou.aliyuncs.com/aliyun-node/alinode:3-alpine。由于没有提供 Dockerfile,不少小伙伴也是心慌慌,简直就是个黑盒,所以也是一直观望状态。于是我花了一点时间分析了官网镜像,并提取了 Dockerfile 模板,方便大家使用,而且可以随心所欲的魔改。模板在这 alinode Dockerfile 官网镜像模板,里面有详细分析原理和提取 Dockerfile 的文章,想知道原理的可以看看。好了,现在 Dockerfile 也有了,没有理由不用了吧。那么开始使用 docker alindoe,由于大家使用 docker 的姿势不同,所以还是参考官网文档。如果你使用 pm2 的,也可以试试我魔改过的版本 docker-alinode创建应用,监控 node 项目上面简单说了 tnvm 和 docker 的安装使用方法,但还没说具体怎么使用。接下来正式开始接入 alinode 平台了。创建应用打开 https://node.console.aliyun.com,没有账号的,去注册个账号,反正免费的。创建应用。复制 App ID 和 App Secret 的值。在你的项目根目录创建一个配置,配置名随便,我这里用 alinode.config.json 演示,写入如下配置。{ “appid”: “76675”, “secret”: “0342cccd7ed8fc29a0f97e59f871d020533385f3”}ps: 详细配置,请参阅 @alicloud/agenthub 模块我这里的目录结构如下:├── node_modules├── alinode.config.json├── app.js├── package.json└── yarn.locktnvm 版本使用先执行 agenthub start alinode.config.json 命令,开启上报工具。然后启动你的应用:ENABLE_NODE_LOG=YES node app.js # 直接启动# 或者ENABLE_NODE_LOG=YES pm2 start pm2.js # PM2 启动# 或者其他任何方式,但要加上 ENABLE_NODE_LOG=YES 开启日志。我之前尝试将 ENABLE_NODE_LOG 写到 pm2 配置中,结果无效,然后也没去研究为什么,如果有大佬知道原因还请告知。然后等待1分钟后,即可在平台上看到监控数据了。agenthub 默认上报时间是可以改的,自己根据官网文档修改吧。docekr 版本使用docker 版本可以省略创建 alinode.config.json 配置文件,直接参数配置。官方镜像docker run -d \ -p 8000:8000 \ -w /app \ -v $PWD:/app \ -e “APP_ID=76675” \ -e “APP_SECRET=0342cccd7ed8fc29a0f97e59f871d020533385f3” \ -h my-alinode \ –name my-alinode \ registry.cn-hangzhou.aliyuncs.com/aliyun-node/alinode:3-alpine \ node app.js使用我封装的镜像,内置 pm2docker run -d \ -p 8000:8000 \ -v $PWD:/app \ -e “APP_ID=76675” \ -e “APP_SECRET=0342cccd7ed8fc29a0f97e59f871d020533385f3” \ -h my-alinode \ –name my-alinode \ toomee/alinode:3-alpine \ pm2-runtime start app.jsps: docker 中使用 pm2-runtime 而不是 pm2 命令。ps: 如果你的 pm2 配置是 ecosystem.config.js,那么可以省略最后一行。查看监控数据启动后等待1分钟,即可看到监控数据。如图,已经有数据了。我们压测一下,然后看看数据。点击实例面板,可以看到进程基本信息,这里只有一个进程,如果你开了多进程,都会在这里显示的。下面的 进程指标分布中 好像显示所有 node 进程,可以看到他把 agenthub 进程也显示出来了。由于测试项目刚启动没啥数据,所以用我们线上项目截了张图。可以看到 内存 cpu 会有两条曲线,一条是系统占用量,一条是 node 占用量。还有 异常日志、Trace、模块依赖 你会发现没数据,根据 agenthub 官网配置配置好后,即可看到数据。慢 HTTP 日志 是访问你要用和你请求后台接口的那些较慢的请求记录。有了这些数据,就可以帮助我们优化应用以及快速定位到问题了。抓取性能数据在 进程数据 页面右下角有个 抓取性能数据 区域,这些就是帮助我们排查 cpu 莫名标高,内存泄漏 等问题的工具。这里就不介绍了,等有空的时候单独写几篇,或者看 官方博客 上的例子。小结至此 alinode 基本使用算是差不多了。哪怕高级功能暂时不会用,那用 alinode 来当个监控也不错啊,在报警面板中配置报警规则,比如 cpu 飚高,load 飚高后报警,支持钉钉机器人推送,非常方便。这次介绍了 alinode 的 tnvm 和 docker 版本的使用方法。目的是安利更多的人来使用,为了你们的项目健康,也为了 alinode 和 node 社区的发展。如果有一天 node 官网内置了 alinode 的功能,并提供的 web监控平台 源码,那就可以搭建属于自己的监控环境了。等到 alindoe 开源后,这一天应该不会远的。 ...

November 2, 2018 · 2 min · jiezi

基于Docker搭建Jumpserver堡垒机操作实践

一、背景笔者最近想起此前公司使用过的堡垒机系统,觉得用的很方便,而现在的公司并没有搭建此类系统,想着以后说不定可以用上;而且最近也有点时间,因此来了搭建堡垒机系统的兴趣,在搭建过程中参考了比较多的文档,其中最详细的还是官方文档,地址如下所示:Jumpserver 文档二、操作概要1. 系统运行2. 配置入门3. 测试验证三、系统运行在官方文档中安装堡垒机有很多种方法,这让笔者有些纠结,另外而且在不同系统中安装方法也不一致,不过正在徘徊不定时,发现一种通用的安装方法,便是采用docker进行安装,因此本文中笔者将以docker安装为例3.1 下载镜像在docker官方镜像库当中并没有收录jumpserver,因此下载镜像命令如下所示:docker pull registry.jumpserver.org/public/jumpserver:1.0.0下载过程可能比较慢,笔者大约花费了14分钟才将其下载完成,下载完成后结果如下所示1.0.0: Pulling from public/jumpserveraf4b0a2388c6: Pull completeaa66a3d10fd2: Pull complete1d4c6a27f2ac: Pull complete2490267572de: Pull completeb00f1599768d: Pull complete398fc903cdc3: Pull completef8490bbfc09a: Pull complete86d238b365f5: Pull complete2cd3b1ef59b2: Pull complete4a21434eeb73: Pull completeae8cf3e909e0: Pull complete7c440776471a: Pull complete0a5e895f91af: Pull completeb86672241685: Pull completeaf16a4945f95: Pull complete0374e723cd6c: Pull completee18b86849df9: Pull complete648aa832cb74: Pull completeb52364a5c704: Pull completeDigest: sha256:0f26e439c492ac52cbc1926aa950a59730607c947c79557ab3da51bfc2c7b5d4Status: Downloaded newer image for registry.jumpserver.org/public/jumpserver:1.0.03.2 运行镜像下载之后笔者需要将下载下来的容器运行起来,为了防止80端口被宿主机其他进程所占用,因此将容器端口映射到宿主机的8011上,运行命令如下所示:docker run –name jms_server -d -p 8011:80 -p 2222:2222 registry.jumpserver.org/public/jumpserver:1.0.0在参数当中因为有加入后台运行参数-d,容器运行之后终端不会进入容器bash中,而且当命令执行成功之后,docker将会返回容器ID,如果返回信息则可能出现了异常错误,正常返回结果如下所示4709a7d85af28bf05a63fb3e42541a41c30edda6668fd54a446cfab006c35b9e3.3 运行检查容器运行之后,笔者需要对其进行检测确保运行成功,检查方式有两个,首先观察容器是否正常运行,然后是检查堡垒机是否能被浏览器所访问首先通过如下命令可以查看当前正在运行的容器docker ps如果容器正常运行将会出现刚在笔者所运行的堡垒机容器ID,正常返回结果参考如下CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES4709a7d85af2 registry.jumpserver.org/public/jumpserver:1.0.0 “/opt/start_jms.sh” 8 minutes ago Up 8 minutes 443/tcp, 0.0.0.0:2222->2222/tcp, 0.0.0.0:8011->80/tcp jms_server在返回结果当中可以看到之前docker返回的容器ID正处于运行状态,便可以确定容器运行正常,接着笔者还需要通过浏览器来检测是否运行成功,使用浏览器打开如下地址http://127.0.0.1:8011/当浏览器出现如下界面时,则基本代表成功四、配置入门在确定系统正常运行之后,接下来就可以对系统进行一些配置,堡垒机配置比较简单,下面的配置是将是使用堡垒机最为基础的一些配置,配置主要是添加一些资产进行管理,这便需要添加管理用户、系统普通用户、账户授权等操作。4.1 登录系统在前面的检验运行的截图当中可以看到需要登录,而账号和密码笔者并没有在官方文档中所看到,笔者随手一尝试,发现用户名和密码分别是admin与admin,如下图所示登录成功之后,进入系统看到的界面如下图所示4.2 管理用户接下来笔者需要添加一些资产,添加资产的前提条件是有一个管理用户,这个管理用户是资产的最高权限账户,堡垒机之后会使用此账户来登录并管理资产,和获取一些统计信息,笔者在资产管理->管理用户列表中点击创建系统用户按钮,便来到了创建管理用户的页面,如下图所示在表单中可以看见必须填写用户名,和认证所用的密码或私钥,按照真实情况去填写,比如笔者的资产最高权限账户是song,密码123456Ab,那么就如实填写上去。4.3 资产管理在添加管理用户之后,便可以添加资产了,添加资产也非常的简单,在资产列表点击创建资产按钮,便来到了添加资产的页面,如下图所示添加资产需要填写,资产的IP地址,以及ssh的端口号,以及选择资产的操作系统类型,并且选择用哪一个管理用户4.4 系统用户在资产管理下还有一个系统用户管理,这个系统用户的使用场景是,有时候需要在很多个目标资产中创建一个普通账户,这时候肯定是十分麻烦;此时便可以通过堡垒机上的系统用户管理来创建一个系统用户;然后下发到目标资产中,这样一来就不需要去目标主机一个个登录然后去创建,因此非常方便,添加系统用户如下图所示创建系统用户需输入需要创建的账号,以及选择认证的方式,默认为秘钥方式,也可以将选择框选中去掉,通过密码来认证。五、测试验证在前面的配置步骤操作完毕后,便可以进行一些常规功能验证,以此来加深对jumpserver系统的了解,这些功能测试点有 资产连接测试、用户授权、Web终端、在线会话、命令记录等功能。5.1 连接测试连接测试的目的是检查资产是否可以被堡垒机所访问,可以在资产列表点击资产名称,便可以进入资产详情页面,右侧有两个按钮,点击刷新按钮,正确配置的参考效果如下图所示如果能看到左侧的硬件信息发生了变更,就代表此前配置的管理用户没有问题,否则会弹出错误提示框;5.2 用户授权当配置资产后,如果想在堡垒机中直接连接终端就还需要给用户授权,授权分为两个步骤,第一步是给web终端账户授权,在会话管理->终端管理,如下图所示第二步则是给用户自己本身授权,在授权管理->资产权限->创建权限规则中做好相应配置,如下图所示5.3 web终端当给用户授权之后,用户便可以会话管理->Web终端中与系统进行交互,如下图所示5.4 在线会话有些时候想看谁在操作服务器,可以很轻松的通过在线会话功能来查看当前有哪些用户在操作终端,在会话管理->在线会话列表中进行查看,如下图所示5.5 命令记录笔者觉得堡垒机最大的作用之一便是审计,如果想知道某个用户在系统中执行了那些命令,可以很方便的在会话管理->命令记录中进行查看,如下图所示六、 图书推荐如果对笔者的实战文章较为感兴趣,可以关注笔者新书《PHP Web安全开发实战》,现已在各大平台上架销售,封面如下图所示作者:汤青松微信:songboy8888日期:2018-10-30 ...

October 30, 2018 · 1 min · jiezi

Docker 中快速安装tensorflow环境

Docker 中快速安装tensorflow环境,并使用TensorFlow。一、下载TensorFlow镜像docker pull tensorflow/tensorflow二、 创建TensorFlow容器docker run –name corwien-tensortflow -it -p 8888:8888 -v /Users/kaiyiwang/Code/ai/notebooks:/notebooks/data tensorflow/tensorflow命令说明docker run 运行镜像,–name 为容器创建别名,-it 保留命令行运行,-p 8888:8888 将本地的8888端口 http://localhost:8888/ 映射,-v /Users/kaiyiwang/Code/ai/notebooks:/notebooks/data 将本地的/Users/kaiyiwang/Code/ai/notebooks文件夹挂载到新建容器的/notebooks/data下(这样创建的文件可以保存到本地/Users/kaiyiwang/Code/ai/notebooks)tensorflow/tensorflow 为指定的镜像,默认标签为latest(即tensorflow/tensorflow:latest)执行上边的命令:我们可以看到,创建了TensorFlow容器,并给了一个默认登录JupiterNotebook的页面。我们可以通过下面的命令在新的命令窗口看正在执行的容器,及容器所对应的映射端口docker ps三、开启TensorFlow容器1.可以直接从命令行中右键打开连接,或者在浏览器中输入http://127.0.0.1:8888,然后将命令行中的token粘贴上去。四、开始TensorFlow编程1、点击登录进去可以看到界面了,并且可以new一个项目2、tensorflow示例源码解读from future import print_function#导入tensorflowimport tensorflow as tf#输入两个数组,input1和input2然后相加,输出结果with tf.Session(): input1 = tf.constant([1.0, 1.0, 1.0, 1.0]) input2 = tf.constant([2.0, 2.0, 2.0, 2.0]) output = tf.add(input1, input2) result = output.eval() print(“result: “, result)3、运行程序,输出的结果为(运行成功)result: [ 3. 3. 3. 3.]五、相关命令1、关闭或开启TensorFlow环境#关闭tensorflow容器docker stop corwien-tensortflow#开启TensorFlow容器docker start corwien-tensortflow#浏览器中输入 http://localhost:8888/2、文件的读写权限修改#查看读写权限ls -l#将tensorflow 变为属于corwien(系统默认)用户sudo chown -R corwien tensorflow/#将tensorflow 变为属于corwien(系统默认)用户组sudo chgrp -R corwien tensorflow/ ...

October 27, 2018 · 1 min · jiezi

SpringCloud微服务部署

微服务的其中一个特点就是有许许多的粒度小(功能单一,比如用户管理,短信发送管理,邮件发送管理,文件管理等)、能独立部署、扩展、运行的小应用,可以称为api,也就是服务提供者。api之间可以相互调用,但更多的是供app调用,比如学生管理系统,它是面向用户的,是许许多多功能的集合体,它需要调用许多api完成业务功能,所以这学生管理系统可以称为app。eureka的作用传统的单体应用开发,就是将api和app的代码全部集成在一起,在同一个进程中运行,对应java web通俗的说就是全部打包在一个war中部署在一个tomcat中运行。而微服务的每个api,app都拥有自己的进程,也就是都有自己的tomcat,某个api挂了,不影响其他api和app运行。api是采取restfull风格暴漏出去的,所以app(api)调用api时,采取http://ip:端口这形式进行通讯。那么问题来了,如果有很多app都调用了某个api,如果api的ip或端口发生了更改,如果app中对api的ip和端口等信息是写在配置文件的,难道要通知每个app,说这个api的地址和端口变了,app要进行修改重新编译部署(这是举例子而已,实际情况有些企业对常量的配置可能写配置文件,也可能写数据库)。这太麻烦了,如果api的地址和端口有发生变化,app能及时获知自行变更,那就好了。还有个弊端,就是api是否挂了,也没法直观观察到。所以,如果在app和api之间增加个服务管理中心,api像服务管理中心注册信息,app从服务管理中心获取api的信息,api有个唯一标识,api有变更的时候通知服务管理中心,服务管理中心通知相关的app或者app定时从服务管理中心获取最新的api信息,同时服务管理中心具有很高的稳定性、可靠性。eureka就是为了这样的一个服务管理中心,下面是我根据自己的理解画的一张图。下面这张是官方的架构图1.Application Service 相当于服务提供者/api2.Application Client 相当于服务消费者/app3.Make Remote Call,其实就是实现服务的使用/比如httpClient,restTemplate4.us-east-1 Eureka 集群服务5.us-east-1c、us-east-1d、us-east-1e 就是具体的某个eurekaEureka:是纯正的 servlet 应用,需构建成war包部署使用了 Jersey 框架实现自身的 RESTful HTTP接口peer之间的同步与服务的注册全部通过 HTTP 协议实现定时任务(发送心跳、定时清理过期服务、节点同步等)通过 JDK 自带的 Timer 实现内存缓存使用Google的guava包实现eureka集群搭建和eureka类似功能的有zookeeper,etcd等。spring boot已经集成了eureka,所以我们可以像spring boot那样搭建环境,部署运行。我是在window10下使用eclipse学习的。准备工作。在修改hosts文件,在最后面加上(位置:C:WindowsSystem32driversetc)127.0.0.1 01.eureka.server 127.0.0.1 02.eureka.server 127.0.0.1 03.eureka.servereclipse下创建个普通的maven项目eureka-server。pom.xml <project xmlns=“http://maven.apache.org/POM/4.0.0" xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven- 4.0.0.xsd”> <modelVersion>4.0.0</modelVersion> <groupId>com.fei.springcloud</groupId> <artifactId>springcloud-eureka-server</artifactId> <version>0.0.1-SNAPSHOT</version> <description>eureka服务端</description> <!– 依赖仓库 设置从aliyun仓库下载 –> <repositories> <repository> <id>alimaven</id> <url>http://maven.aliyun.com/nexus/content/repositories/central/</url> <snapshots> <enabled>true</enabled> </snapshots> <releases> <enabled>true</enabled> </releases> </repository> </repositories> <!– 插件依赖仓库 –> <pluginRepositories> <pluginRepository> <id>alimaven</id> <url>http://maven.aliyun.com/nexus/content/repositories/central/</url> <snapshots> <enabled>true</enabled> </snapshots> <releases> <enabled>true</enabled> </releases> </pluginRepository> </pluginRepositories> <properties> <!– 文件拷贝时的编码 –> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <!– 编译时的编码 –> <maven.compiler.encoding>UTF-8</maven.compiler.encoding> <java.version>1.8</java.version> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath /> </parent> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.RELEASE</version> <type>pom</type> <scope>import</scope><!– 这个不能丢 –> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>启动类Application.javapackage com.fei.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @EnableEurekaServer @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }注意注解:@EnableEurekaServer,表明这是server服务配置文件application.propertieslogging.config=classpath:logback.xml logging.path=d:/logs ##tomcat set### # eureka的默认端口是8761 server.port=8081 server.session-timeout=60 ########### spring.application.name=eureka-server-01 ####下面2个一定要false,因为这程序是要作为服务端但是jar中存在eureka-client.jar,所以要false,否则启动会报错的 #是否注册到eureka eureka.client.register-with-eureka=false #是否获取注册信息 eureka.client.fetch-registry=false #为了便于测试,取消eureka的保护模式,如果启动的话,比如api提供者关闭了,但是eureka仍然保留信息 eureka.server.enable-self-preservation=false #服务名称 eureka.instance.hostname=01.server.eureka #eureka的服务地址,/eureka是固定的 eureka.client.serviceUrl.defaultZone=http://02.server.eureka:8082/eureka/,http://03.server.eureka:8083/eureka/ 注意:eureka.client.serviceUrl.defaultZone的配置,如果是01,则填写02、03的地址和端口;如果是02,则填写01、03的地址和端口,也就是说让01,02,03这3个eureka服务能相互间同步数据,如果是01->02->03->01,则api提供者注册信息到01时,01会同步数据到02,但02不会同步到03,01也不会同步到03,也就是说03缺数据了。看源码,服务的注册信息不会被二次传播,看PeerAwareInstanceRegistryImpl.java启动01 eureka,然后修改application.properties,变为02 eureka的配置logging.config=classpath:logback.xml logging.path=d:/logs ##tomcat set### # eureka的默认端口是8761 server.port=8082 server.session-timeout=60 ########### spring.application.name=eureka-server-02 ####下面2个一定要false,因为这程序是要作为服务端,但是jar中存在eureka-client.jar,所以要false,否则启动会报错的 #是否注册到eureka eureka.client.register-with-eureka=false #是否获取注册信息 eureka.client.fetch-registry=false #为了便于测试,取消eureka的保护模式,如果启动的话,比如api提供者关闭了,但是eureka仍然保留信息 eureka.server.enable-self-preservation=false #服务名称 eureka.instance.hostname=02.server.eureka #eureka的服务地址,/eureka是固定的 eureka.client.serviceUrl.defaultZone=http://01.server.eureka:8081/eureka/,http://03.server.eureka:8083/eureka/然后执行启动类,03也是一样的操作。浏览器访问http://01.server.eureka:8081/(或者http://02.server.eureka:8082/,或者http://03.server.eureka:8083/)看到api提供者创建个普通的maven项目eureka-api,该api是个用户服务提供者。采取spring boot开发模式pom.xml<project xmlns=“http://maven.apache.org/POM/4.0.0" xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven- 4.0.0.xsd”> <modelVersion>4.0.0</modelVersion> <groupId>com.fei.springcloud</groupId> <artifactId>springcloud-eureka-server</artifactId> <version>0.0.1-SNAPSHOT</version> <description>eureka服务端</description> <!– 依赖仓库 设置从aliyun仓库下载 –> <repositories> <repository> <id>alimaven</id> <url>http://maven.aliyun.com/nexus/content/repositories/central/</url> <snapshots> <enabled>true</enabled> </snapshots> <releases> <enabled>true</enabled> </releases> </repository> </repositories> <!– 插件依赖仓库 –> <pluginRepositories> <id>alimaven</id> <url>http://maven.aliyun.com/nexus/content/repositories/central/</url> <snapshots> <enabled>true</enabled> </snapshots> <releases> <enabled>true</enabled> </releases> </pluginRepository> </pluginRepositories> <properties> <!– 文件拷贝时的编码 –> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <!– 编译时的编码 –> <maven.compiler.encoding>UTF-8</maven.compiler.encoding> <java.version>1.8</java.version> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath /> </parent> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.RELEASE</version> <type>pom</type> <scope>import</scope><!– 这个不能丢 –> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 它和eureka 服务端,有个依赖是不一样的。application.propertieslogging.config=classpath:logback.xml logging.path=d:/logs ##tomcat set### # eureka的默认端口是8761 server.port=9081 server.session-timeout=60 ########### spring.application.name=api-user-server #像eureka服务注册信息时,使用ip地址,默认使用hostname eureka.instance.preferIpAddress=true #服务的instance-id默认默认值是${spring.cloud.client.hostname:${spring.aplication.name}:${spring.application.instance_id:${server.port}} , #也就是机器主机名:应用名称:应用端口 eureka.instance.instance-id=${spring.cloud.client.ipAddress}:${server.port} #eureka的服务地址 eureka.client.serviceUrl.defaultZone=http://01.server.eureka:8081/eureka/Application.java package com.fei.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @EnableEurekaClient @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } @EnableEurekaClient,不管是消费者还是提供者,对应eureka server来说都是客户端client写个普通的controller,UserProvider.java.提供个根据id获取用户信息的接口 package com.fei.springcloud.provider; import javax.servlet.http.HttpServletRequest; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/user”) public class UserProvider { @GetMapping(value="/find/{id}”) public String find(@PathVariable(“id”) String id,HttpServletRequest request){ //实际项目中,这里可以使用JSONObject,返回json字符串 //为了便于测试消费者app的负载均衡,返回服务端端口 String s = “张三”+” 服务端端口:"+request.getLocalPort(); return s; } } 执行Application.java,将application.properties的端口修改为9082,再次执行浏览器访问http://01.server.eureka:8081/Application就是文件中定义的spring.application.name=api-user-server,它会自动转为大写如果想免费学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java进阶群:478030634,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。app消费者在Spring Cloud Netflix中,使用Ribbon实现客户端负载均衡,使用Feign实现声明式HTTP客户端调用——即写得像本地函数调用一样.ribbo负载均衡的app消费者创建个普通的maven项目eureka-app-ribbo.pom.xml<project xmlns=“http://maven.apache.org/POM/4.0.0" xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven- 4.0.0.xsd”> <modelVersion>4.0.0</modelVersion> <groupId>com.fei.springcloud</groupId> <artifactId>springcloud-eureka-app-ribbo</artifactId> <version>0.0.1-SNAPSHOT</version> <description>eureka消费者ribbo</description> <!– 依赖仓库 设置从aliyun仓库下载 –> <repositories> <repository> <id>alimaven</id> <url>http://maven.aliyun.com/nexus/content/repositories/central/</url> <snapshots> <enabled>true</enabled> </snapshots> <releases> <enabled>true</enabled> </releases> </repository> </repositories> <!– 插件依赖仓库 –> <pluginRepositories> <pluginRepository> <id>alimaven</id> <url>http://maven.aliyun.com/nexus/content/repositories/central/</url> <snapshots> <enabled>true</enabled> </snapshots> <releases> <enabled>true</enabled> </releases> </pluginRepository> </pluginRepositories> <properties> <!– 文件拷贝时的编码 –> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><!– 编译时的编码 –> <maven.compiler.encoding>UTF-8</maven.compiler.encoding> <java.version>1.8</java.version> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath /> </parent> <dependencies> <!– 客户端负载均衡 –> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> </dependency> <!– eureka客户端 –> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <!– spring boot实现Java Web服务 –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.RELEASE</version> <type>pom</type> <scope>import</scope><!– 这个不能丢 –> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> application.propertieslogging.config=classpath:logback.xml logging.path=d:/logs ##tomcat set### # eureka的默认端口是8761 server.port=7081 server.session-timeout=60 ########### spring.application.name=app-user #像eureka服务注册信息时,使用ip地址,默认使用hostname eureka.instance.preferIpAddress=true #服务的instance-id默认默认值是${spring.cloud.client.hostname}${spring.application.name}:${spring.application.instance_id:${server.port}} , #也就是机器主机名:应用名称:应用端口 eureka.instance.instance-id=${spring.cloud.client.ipAddress}:${server.port} #eureka的服务地址 eureka.client.serviceUrl.defaultZone=http://01.server.eureka:8081/eureka/,http://02.server.eureka:8082/eureka/,http://03.server.eureka:8083/eureka/UserController.java logging.config=classpath:logback.xml logging.path=d:/logs ##tomcat set### # eureka的默认端口是8761 server.port=7081 server.session-timeout=60 ########### spring.application.name=app-user #像eureka服务注册信息时,使用ip地址,默认使用hostname eureka.instance.preferIpAddress=true #服务的instance-id默认默认值是${spring.cloud.client.hostname} :${spring.application.name}:${spring.application.instance_id:${server.port}} , #也就是机器主机名:应用名称:应用端口 eureka.instance.instance-id=${spring.cloud.client.ipAddress}:${server.port} #eureka的服务地址 eureka.client.serviceUrl.defaultZone=http://01.server.eureka:8081/eureka/ ,http://02.server.eureka:8082/eureka/,http://03.server.eureka:8083/eureka/ 使用restTemplate需要自己拼接url启动类Application.javapackage com.fei.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @EnableEurekaClient @SpringBootApplication public class Application { @Bean //定义REST客户端,RestTemplate实例 @LoadBalanced //开启负债均衡的能力 RestTemplate restTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }eureka服务浏览器访问http://127.0.0.1:7081/user/find看到信息“张三 服务端端口:9081”,刷新浏览器看到“张三 服务端端口:9082”,说明的确是有负载均衡。但是访问外网的时候,http://127.0.0.1:7081/user/test,也就是域名不在eureka注册过的,就不行了。以后再研究下如何解决。feign的app消费者feign可以写个接口,加上相关的注解,调用的时候,会自动拼接url,调用者就像调用本地接口一样的操作。Feign也用到ribbon,当你使用@ FeignClient,ribbon自动被应用。像ribbo创建个项目,或者直接在ribbo项目修改都OK。pom.xml 把ribbo的依赖修改为feign<project xmlns=“http://maven.apache.org/POM/4.0.0" xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven- 4.0.0.xsd”> <modelVersion>4.0.0</modelVersion> <groupId>com.fei.springcloud</groupId> <artifactId>springcloud-eureka-app-feign</artifactId> <version>0.0.1-SNAPSHOT</version> <description>eureka消费者feign</description> <!– 依赖仓库 设置从aliyun仓库下载 –> <repositories> <repository> <id>alimaven</id> <url>http://maven.aliyun.com/nexus/content /repositories/central/</url> <snapshots> <enabled>true</enabled> </snapshots> <releases> <enabled>true</enabled> </releases> </repository> </repositories> <!– 插件依赖仓库 –> <pluginRepositories> <pluginRepository> <id>alimaven</id> <url>http://maven.aliyun.com/nexus/content/repositories/central/</url> <snapshots> <enabled>true</enabled> </snapshots> <releases> <enabled>true</enabled> </releases> </pluginRepository> </pluginRepositories> <properties> <!– 文件拷贝时的编码 –> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <!– 编译时的编码 –> <maven.compiler.encoding>UTF-8</maven.compiler.encoding> <java.version>1.8</java.version> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath /> </parent> <dependencies> <!– Feign实现声明式HTTP客户端 –> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency> <!– eureka客户端 –> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <!– spring boot实现Java Web服务 –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.RELEASE</version> <type>pom</type> <scope>import</scope><!– 这个不能丢 –> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>application.properties和上面一样logging.config=classpath:logback.xml logging.path=d:/logs ##tomcat set### # eureka的默认端口是8761 server.port=7081 server.session-timeout=60 ########### spring.application.name=app-user #像eureka服务注册信息时,使用ip地址,默认使用hostname eureka.instance.preferIpAddress=true #服务的instance-id默认默认值是${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}} , #也就是机器主机名:应用名称:应用端口 eureka.instance.instance-id=${spring.cloud.client.ipAddress}:${server.port} #eureka的服务地址 eureka.client.serviceUrl.defaultZone=http://01.server.eureka8081/eureka/,http://02.server.eureka:8082/eureka/,http://03.server.eureka:8083/eureka/增加个UserService.java接口类package com.fei.springcloud.service; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @FeignClient(“API-USER-SERVER”) public interface UserService { @GetMapping(value="/user/find/{id}”) String find(@PathVariable(“id”) String id); }UserController.javapackage com.fei.springcloud.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.fei.springcloud.service.UserService; @Controller @RequestMapping("/user”) public class UserController { @Autowired private UserService userService; @GetMapping(value = “/find”) @ResponseBody public String find() { //url中对应api提供者的名称,全大写 String s = userService.find(“123”); return s; } }浏览器访问http://127.0.0.1:7081/user/find,得到信息“张三 服务端端口:9081”,刷新,得到“张三 服务端端口:9082”,Feign也用到ribbon,当你使用@ FeignClient,ribbon自动被应用。所以也会负载均衡ribbo负载均衡策略选择AvailabilityFilteringRule:过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的的后端server(active connections 超过配置的阈值) | 使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个server的运行状态RandomRule:随机选择一个serverBestAvailabl:选择一个最小的并发请求的server,逐个考察Server,如果Server被tripped了,则忽略RoundRobinRule:roundRobin方式轮询选择, 轮询index,选择index对应位置的serverWeightedResponseTimeRule:根据响应时间分配一个weight(权重),响应时间越长,weight越小,被选中的可能性越低RetryRule:对选定的负载均衡策略机上重试机制,在一个配置时间段内当选择server不成功,则一直尝试使用subRule的方式选择一个可用的serverZoneAvoidanceRule:复合判断server所在区域的性能和server的可用性选择serverResponseTimeWeightedRule:作用同WeightedResponseTimeRule,二者作用是一样的,ResponseTimeWeightedRule后来改名为WeightedResponseTimeRule在app消费者的application.properties配置文件中加入#ribbo负载均衡策略配置,默认是依次轮询 API-USER-SERVER.ribbon.NFLoadBalancerRuleClassName=com. netflix.loadbalancer.RandomRule其中API-USER-SERVER是api服务提供者的服务名称,也就是说,可以给每个不同的api服务提供者配置不同的复制均衡策略,验证就不贴图了负载均衡策略在消费端配置的缺点在上面的例子中,ribbon的负载均衡是在消费端完成的。流程是这样的:提供者服务A集群,启动2个进程A1,A2,都注册到eureka,app消费端根据api服务者名称获取到A1,A2的具体连接地址,ribbon就对A1,A2进行负载均衡。缺点:1) 如果所有的app消费端的配置策略不好,导致绝大部分的请求都到A1,那A1的压力就大了。也就是说负载策略不是有api提供者所控制的了(这里就不说A1,A2所在的服务器哪个性能更好了,因为如果app/api都是在Docker中运行,k8s负责资源调配的话,可以认为每个服务的进程所在的docker配置是一样的,比如A服务对应的A1,A2系统环境是一致的,B服务对应的B1,B2,B3系统环境是一致的,只是所对应的宿主机服务器估计不一样而已)。2)如果api提供者需要开放给第三方公司的时候,总不能把A1,A2告诉第三方吧,说我们这A服务集群了,有A1,A2,你随意吧。我们实际项目中的做法,都是每个提供者服务都有自己的nginx管理自己的集群,然后把nginx的域名提供给app消费者即可。之所以每个服务提供者都有自己的nginx,是因为docker被k8s管控的时候,ip都是变化的,需要更新到nginx中。如果A,B都共用一个nginx,那A重构建部署的时候,A1,A2的ip变化了,需要更新到nginx中去,那如果导致nginx出现问题了,岂不是影响到B的使用了,所以A,B都有自己的nginx。那spring cloud没有解决方案了吗?有。spring cloud集成了zuul,zuul服务网关,不但提供了和nginx一样的反向代理功能,还提供了负载均衡、监控、过滤等功能,在后面学习zuul的时候,我们再学习。如果想免费学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java进阶群:478030634,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。docker+k8s实现的devOps平台(paas平台),构建好镜像后,部署的时候,k8s负责调控资源,将docker分配到不同的节点服务器,同时将docker的ip相关信息更新到nginx。这是自动化的,不需要开发人员手动操作docker,nginx配置。 ...

October 26, 2018 · 4 min · jiezi

Rails Docker开发环境配置

rails mysql redis 的开发环境首先构建自己的镜像Dockerfile.developmentFROM ruby:2.3.4-slimRUN apt-get update && apt-get install -y \ build-essential \ nodejs \ libmysqlclient-devRUN mkdir -p /appWORKDIR /appCOPY Gemfile Gemfile.lock /app/RUN gem install bundler && bundle install –jobs 20 –retry 5COPY . /appEXPOSE 4000ENTRYPOINT [“bundle”, “exec”]CMD [“rails”, “server”, “-b”, “0.0.0.0”, “-p”, “4000”]docker-compose.yml 配置version: ‘3’services: mysql: image: mysql:5.7.17 command: –sql-mode="" restart: always volumes: - ./mysql_data/:/var/lib/mysql ports: - “3306:3306” environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: shiji_development redis: image: redis command: redis-server volumes: - ./redis_data:/data ports: - 6379:6379 web: build: context: . dockerfile: Dockerfile.development command: bash -c “rm -f tmp/pids/server.pid && bundle exec rails s -p 4000 -b ‘0.0.0.0’” stdin_open: true tty: true volumes: - .:/app ports: - “4000:4000” depends_on: - mysql - redis ...

October 26, 2018 · 1 min · jiezi

docker指令学习记录

前言本文为学习整理和参考文章,不具有教程的功能。其次,后面将会陆续更新各种应用的容器化部署的实践,如MySQL容器化,Jenkins容器化,以供读者参考。镜像获取docker pull [options] [Docker Registry地址]<仓库名>:<标签>-a, –all-tags: 下载该镜像的所有版本Docker Registry地址默认为Docker Hub,一般格式为IP:端口号仓库名为两段式 <用户名>:<软件名> 默认用户名为library标签不填则默认为latest列出镜像docker images [options] [Repository[:tag]]默认情况会展示所有最终镜像,如果加上了镜像名,则会展示该镜像的所有信息-a, –all: 展示所有镜像,包括中间层-f, –filter filter: 根据某种条件对镜像进行筛选–format string: 使用go的模板语法-q, –quiet: 只返回镜像的IDdocker images -f since=mongo:3.2 #查看mongo3.2版本之后建立的镜像,如果是要在之前,则使用beforedocker images –format “{{.ID}}:{{.Repository}}” #输出结构为ID:Repository虚悬镜像虚悬镜像是指既没有仓库名,也没有标签的镜像。这种镜像的产生常常由于当前的仓库名和标签被更新版本占用,导致当前境像失效。docker images -f danling=true #列出所有虚悬镜像docker rmi $(docker images -q -f dangling=true) #利用复合指令删除虚悬镜像commit镜像commit会将容器的存储层保存下来成为新的镜像docker commit [options] <容器ID或容器名> [<仓库名>[:<标签>]]-a, –author string: 容器所有者-c, –change list: 在容器上执行Dockerfile指令-m, –message string: 提交信息-p, –pause: 提交过程中停止容器的运行,默认为truedocker history IMAGE #显示镜像的历史记录docker diff CONTAINER #查看容器的改动尽量不要使用commit指令构建镜像Dockerfile构建镜像利用Dockerfile构建镜像。docker build [options] PATH | URL | –f, –file string: Dockerfile的路径–rm: 成功构建后删除中间镜像-t, –tag: 以name:tag的形式为镜像命名docker build -t nginx:v3 . #执行当前目录下的Dockerfile并构建镜像,新的镜像名为nginx:v3docker build https://…… #直接从github构建,会自动clone这个项目,切换到指定分支(默认为master),并进入指定目录进行构建最后的路径是指镜像构建的上下文,docker在build的时候会把该上下文中的而所有内容全部打包上传给docker引擎。当在Dockerfile中需要引用相对路径时,就是以该上下文作为当前指令执行的目录。可以编写.dockerignore文件来剔除无需打包的文件。在默认情况下,如果不指定Dockerfile的位置,就会从构建的上下文寻找Dockerfile来执行FROM指定基础镜像,Dockerfile的第一行必须制定基础镜像RUN执行命令。RUN指令会新建一层并在其上执行指令,指令完成之后再commit该镜像。所以RUN指令中的内容应当尽可能合并,并且记得清除冗余的内容如缓存等。RUN <指令>RUN [“可执行文件”, “参数1”, “参数2”]RUN mkdir newDir \ && touch newFileCOPY将构建上下文中源路径中的内容复制到目标路径之下。可以使用通配符。如果目标目录不存在,容器会帮助创建。复制过程不改变文件属性。COPY 源路径 目标路径COPY [“源路径”,…,“目标路径”]COPY hom* /mydir/CMD默认的容器的主进程的启动命令,在运行时可以指定新的命令来替代镜像设置中的默认命令。比如ubuntu的默认指令是/bin/bash。如果使用第一种形式,则会以sh -c的形式执行,这样就能够得到环境变量。容器中的应用都应该前台执行。CMD <命令>CMD [“可执行文件”, “参数一”, “参数二”, …]CMD [“参数一”, “参数二”…]CMD [“nginx”, “-g”, “daemon off;"]docker run -it ubuntu #直接进入bash,因为默认指令为/bin/bashdocker run -it ubuntu /etc/os-release #默认指令变成/etc/os-releaseENTRYPOINT指定容器启动程序及参数,当指定了ENTRYPOINT之后,CMD的含义就变成了ENTRYPOINT的参数。从而实现我们在build镜像时可以根据配置修改启动指令的参数。在docker run运行时可以用–entrypoint覆盖ENTRYPOINT “CMD"ENTRYPOINT [“可执行文件”, “参数一”, “参数二”…]ENV设置环境变量ENV KEY VALUEENV KEY1=VALUE2 KEY2=VALUE2ARG同ENV,设置环境变量并为其提供默认值,不同的是在容器运行时,这些值将不存在。在运行时可以用–build-arg <参数名>:<值>覆盖ARG <参数名>[=默认值]VOLUMN指定匿名卷,防止用户忘记挂载,运行时用-v HOST_DIR/CONTAINER_DIR进行覆盖VOLUMN PATHEXPOSE声明运行时容器提供的服务端口,运行时应用并不会因为这个声明而打开这个端口。docker run -P时会对声明的端口随机映射EXPOSE 端口一 端口二WORKDIR指定容器之后各层的工作目录。因为本层的cd并不会顺带到下一层。WORKDIR PATHUSER改变之后层执行RUN,ENTRYPOINT等指令的身份RUN groupadd -r redis && useradd -r -g redis redisUSER redisRUN [“redis-server”]ONBUILDONBUILD 其它指令用于构建基础镜像,被引用是才会真正执行。可以提取出重复的部分,方便维护删除docker rmi [options] <image1> [<image2>….] #删除镜像docker rm [options] <container1> [<container2>…] #删除容器进入容器docker attach CONTAINER_NAME查看数据卷信息docker inspect CONTAINER_NAME匿名的数据卷默认位于/var/lib/docker/volumes之下查看容器docker logs [-f] container查看端口映射配置docker port container container_port容器链接–link container_name:alias ...

October 19, 2018 · 1 min · jiezi

Docker入门总结

原文地址: 直接访问Docker是一个虚拟环境容器,可以将应用代码、环境配置、系统环境等一并打包在一起,生成一个镜像,然后就可以发布到任意平台上。与VM的区别VM在物理机的操作系统上建立了一个中间软件层 hypervisor,利用物理机资源,虚拟出多个硬件资源,这些新的虚拟硬件环境、安装的操作系统、相应软件便构成了一台虚拟机而 Docker 对硬件资源,在不同docker container上做了隔离,使得每个docker container拥有不同的环境,同时可以共享硬件资源日常使用的基本概念核心功能简答介绍:Docker是C/S模式images:docker镜像,是Docker run的原材料container: Docker运行的内容,是独立存在的data volumes: 通过数据挂载的方式,实现数据共享network:用户容器与外部、容器之间的通信,常用的方法有端口映射、link等使用流程基本操作docker version: 查看基本版本信息,包括client、server关于镜像的基本操作docker search: 默认在 https://hub.docker.com 中查询镜像,当然可以修改registrydocker pull: 镜像拉取 docker pull imageName:versiondocker push: 镜像提交docker images: 查看本地镜像docker rmi: 删除本地镜像docker build:利用 Dockerfile 制作镜像,例如 docker build -t newImageName -f dockerFile [contextPath]docker commit: 基于运行的 container 制作镜像关于容器的基本操作docker run镜像的运行d: 在后台运行v: 用户数据挂载p: 端口映射,实现外部与容器之间的通信rm: 容器推出时,直接删除容器i: 交互式的方式t: 在容器中启动一个终端docker ps查询当前存在的容器a: 列出所有容器q: 仅出 container iddock exec: 在容器中执行命名,例如可以使用 docker exec -it containerId /bin/bash 进入到容器内部docker stop: 停止容器的运行docker restart: 重新启动容器的运行docker rm: 容器删除DockerFile# 指定基础镜像FROM NODE:10.12## 从本地 copy 文件到镜像中COPY ./ /data/my-node/## 切换 container 的工作目录WORKDIR /data/my/node## 执行命令RUN npm install## 容器的启动命名ENTRYPOINT [“node”, “./index.js”] ...

October 15, 2018 · 1 min · jiezi

猫头鹰的深夜翻译:持久化容器存储

前言临时性存储是容器的一个很大的买点。“根据一个镜像启动容器,随意变更,然后停止变更重启一个容器。你看,一个全新的文件系统又诞生了。”在docker的语境下:# docker run -it centos[root@d42876f95c6a /]# echo “Hello world” > /hello-file[root@d42876f95c6a /]# exitexit# docker run -it centos[root@a0a93816fcfe /]# cat /hello-filecat: /hello-file: No such file or directory当我们围绕容器构建应用程序时,这个临时性存储非常有用。它便于水平扩展:我们只是从同一个镜像创建多个容器实例,每个实例都有自己独立的文件系统。它也易于升级:我们只是创建了一个新版本的映像,我们不必担心从现有容器实例中保留任何内容。它可以轻松地从单个系统移动到群集,或从内部部署移动到云:我们只需要确保集群或云可以访问registry中的镜像。而且它易于恢复:无论我们的程序崩溃对文件系统造成了什么损坏,我们只需要从镜像重新启动一个容器实例,之后就像从未发生过故障一样。因此,我们希望容器引擎依然提供临时存储。但是从教程示例转换到实际应用程序时,我们确实会遇到问题。真实的应用必修在某个地方存储数据。通常,我们将状态保存到某个数据存储中(SQL或是NOSQL)。这也引来了同样的问题。数据存储也是位于容器中吗?理想情况下,答案是肯定的,这样我们可以利用和应用层相同的滚动升级,冗余和故障转移机制。但是,要在容器中运行我们的数据存储,我们再也不能满足于临时存储。容器实例需要能够访问持久存储。如果使用docker管理持久性存储,有两种主流方案:我们可以在宿主机文件系统上指定一个目录,或者是由Docker管理存储:# docker volume create datadata# docker run -it -v data:/data centos[root@5238393087ae /]# echo “Hello world” > /data/hello-file[root@5238393087ae /]# exitexit# docker run -it -v data:/data centos[root@e62608823cd0 /]# cat /data/hello-fileHello worldDocker并不会保留第一个容器的根目录,但是它会保留“data”卷。而该卷会被再次挂载到第二个容器上。所以该卷是持久存储。在单节点系统上这样的方法是ok的。但是在一个容器集群环境下如Kubernetes或是Docker Swarm,情况会变得复杂。如果我们的数据存储容器可能在上百个节点中的任意一个上启动,而且可能从一个节点随时迁移到另一个节点,我们无法依赖于单一的文件系统来存储数据,我们需要一个能够感知到容器的分部署存储的方案,从而无缝集成。容器持久化的需求在深入容器持久化的方案之前,我们应该先了解一下这个方案应该满足什么特性,从而更好的理解各种容器持久化方案的设计思路。冗余将应用移动到容器中并且将容器部署到一个编排环境的原因在于我们可以有更多的物理节点,从而可以支持部分节点当掉。同理,我们也希望持久化存储能够容忍磁盘和节点的崩溃并且继续支持应用运行。在持久化的场景下,冗余的需求更加重要了,因为我们无法忍受任何数据的丢失。分布式冗余的持久化驱动我们使用某种分布式策略,至少在磁盘层面上。但是我们还希望通过分布式存储来提高性能。当我们将容器水平扩展到成百上千个节点上是,我们不希望这些节点竞争位于同一个磁盘上的数据。所以当我们将服务部署到各个区域的环境上来减少用户延时时,我们还希望将存储也同时分布式部署。动态的容器架构持续变更。新版本不断的被构建,更新,应用被添加或是移除。测试用例被创建并启动,然后被删除。在这个架构下,需要能够动态的配置和释放存储。事实上,配置存储应当和我们声明容器实例,服务和网络连通性一样通过声明来实现。灵活性容器技术在飞速发展,我们需要能够引入新的存储策略,并且将应用移植到新的存储架构上。我们的存储策略需要能够支持任何底层架构,从开发人员用于测试的单节点到一个开放的云环境。透明性我们需要为各种类型的应用提供村塾,而且我们需要持续更新存储方案。这意味着我们不应该将应用强关联与一个存储方案。因此,存储需要看上去像是原生的,也就是对上层用户来说仿佛是一个文件系统,或者是某种现有的,易于理解的API。云原生存储另一种说法是我们希望容器存储解决方案是“Cloud Native”(云原生的)。云原生计算组织(CNCF)定义了云原生系统的三个属性。这些属性也适用于存储:容器打包: 我们的物理或虚拟存储位于容器之外,但是我们希望它仅对特定容器课件(这样的话,容器就不会共享存储,除非特殊需求)。除此以外,我们可能希望容器化存储管理软件本身,从而利用容器化来管理和升级存储管理软件。动态管理:对于有状态容器的持久部署,我们需要在无需管理员认为干预的情况下,分配存储给新的容器,并清理失效的存储。面向微服务:当我们定义一个容器的时候,他应当明确的制定对存储的依赖。除此以外,存储管理软件本身应当基于微服务部署,从而更好的实现扩容和异地部署。提供容器存储为了满足容器持久化存储的需求,Kubernetes和Docker Swarm提供了一组声明式资源来声明并绑定持久化存储至容器。这些持久化存储的功能构建与一些存储架构之上。我们首先来看一下这两种环境下是如何支持容器来声明对持久化存储的以来的。Kubernetes在Kubernetes中,容器存活于Pods中。每个pod包含一个或多个容器,它们共享网络栈和持久存储。持久化存储的定义位于pod定义的volumn字段下。该卷可以被挂在到pod的任意一个容器下。比如,一下有一个Kubernetes的Pod定义,它使用了一个emptyDir卷在容器间共享信息。emptyDir卷初始为空,即使pod被迁移到另一个节点上仍将保存下来(这意味着容器的崩溃不会使其消失,但是node崩溃会将其删除)apiVersion: v1kind: Podmetadata: name: hello-storagespec: restartPolicy: Never volumes: - name: shared-data emptyDir: {} containers: - name: nginx-container image: nginx volumeMounts: - name: shared-data mountPath: /usr/share/nginx/html - name: debian-container image: debian volumeMounts: - name: shared-data mountPath: /pod-data command: ["/bin/sh"] args: ["-c", “echo Hello from the debian container > /pod-data/index.html”]如果你将以上内容保存到名为two-containers.yaml并使用kubectl create -f two-containers.yaml将其部署到kubernetes上,我们可以使用pod的IP地址来访问NGINX服务器,并获取新建的index.html文件。这个例子说明了Kubernetes是如何支持在pod中使用volumn字段声明一个存储依赖的。但是,这不是真正的持久化存储。如果我们的Kubernetes容器使用AWS EC2,yaml文件如下:apiVersion: v1kind: Podmetadata: name: webserverspec: containers: - image: nginx name: nginx volumeMounts: - mountPath: /usr/share/nginx/html name: web-files volumes: - name: web-files awsElasticBlockStore: volumeID: <volume-id> fsType: ext4在这个例子中,我们可以重复创建和销毁pod,同一个持久存储会被提供给新的pod,无论容器位于哪个节点上。但是,这个例子还是无法提供动态存储,因为我们在创建pod之前必须先创建好EBS卷。为了从Kubernetes获得动态存储的支持,我们需要另外两个重要的概念。第一个是storageClass,Kubernetes允许我们创建一个storageClass资源来收集一个持久化存储供应者的信息。然后将其和persistentVolumeClaim,一个允许我们从storageClass动态请求持久化存储的资源结合起来。Kubernetes会帮我们向选择的storageClass发起请求。这里还是以AWS EBS为例:kind: StorageClassapiVersion: storage.k8s.io/v1metadata: name: file-storeprovisioner: kubernetes.io/aws-ebsparameters: type: io1 zones: us-east-1d, us-east-1c iopsPerGB: “10”—kind: PersistentVolumeClaimapiVersion: v1metadata: name: web-static-filesspec: resources: requests: storage: 8Gi storageClassName: file-store—apiVersion: v1kind: Podmetadata: name: webserverspec: containers: - image: nginx name: nginx volumeMounts: - mountPath: /usr/share/nginx/html name: web-files volumes: - name: web-files persistentVolumeClaim: claimName: web-static-files如你所见,我们还在使用volume关键字来制定pod所需要的持久化存储,但是我们使用了额外的PersistentVolumeClaim声明来请求Kubernenetes替我们发起请求。总体上,集群管理员会为每一个集群部署一个StorageClass代表可用的底层存储。然后应用开发者会在第一次需要持久存储时指定PersistentVolumeClaim。之后根据应用程序升级的需要部署和更换pod,不会丢失持久存储中的数据。Docker SwarmDocker Swarm利用我们在单节点Docker卷上看到的核心卷管理功能, 从而支持能够为任何节点上的容器提供存储:version: “3"services: webserver: image: nginx volumes: - web-files:/usr/share/nginx/htmlvolumes: web-files: driver: storageos driver-opts: size: 20 storageos.feature.replicas: 2当我们使用docker栈部署时,Docker Swarm会创建web-files卷,仿佛它并不存在。这个卷会被保留,及时我们删除了docker栈。总的来说,我们可以看到Kubernetes和Docker都满足了云原生存储的要求。他们允许容器声明依赖的存储,并且动态的管理存储从而使其在应用需要时可见。无论容器在集群的哪个机器上运行,他们都能够提供持久存储。 ...

October 4, 2018 · 1 min · jiezi

使用Docker部署Spring-Boot+Vue博客系统

在今年年初的时候,完成了自己的个Fame博客系统的实现,当时也做了一篇博文Spring-boot+Vue = Fame 写blog的一次小结作为记录和介绍。从完成实现到现在,也断断续续的根据实际的使用情况进行更新。只不过每次上线部署的时候都觉得有些麻烦,因为我的服务器内存太小,每次即使只更新了前台部分(fame-front)的代码,在执行npm build的时候都还必须把我的后端服务(fame-server)的进程关掉,不然会造成服务器卡死(惨啊)。而且这个项目是前后端分离的,博客前台页面还为了SEO用了Nuxt框架,假如是第一次部署或者要服务器迁移的话,麻烦的要死啊,部署一次的话要以下步骤安装mysql,修改相关配置文件,设置编码时区等,然后重启下载安装java,配置java环境下载安装maven,配置maven环境下载安装nginx,修改配置文件,设计反向代理等启动spring-boot项目打包vue项目,npm install,npm run build等启动nuxt项目,npm install,npm run start等如果能够顺利的完成这七个步骤算是幸运儿了,假如中间哪个步骤报错出了问题,可能还要回头查找哪个步骤出了问题,然后又重新部署。在这些需求面前,Docker就是解决这些问题的大杀器。无论是其虚拟化技术隔离各个容器使其资源互不影响,还是一致的运行环境,以及docker-compose的一键部署,都完美的解决了上述问题。项目地址:FameDocker和Docker-compose安装Docker和Docker-compose的功能和使用可以看线上的一个中文文档Docker — 从入门到实践下面是Centos7安装和配置Docker以及Docker-compose的shell脚本,其他操作系统可以参考修改来安装。其中Docker版本为docker-ce,Docker-compose版本为1.22.0#!/bin/sh### 更新 ###yum -y update### 安装docker #### 安装一些必要的系统工具sudo yum install -y yum-utils device-mapper-persistent-data lvm2# 添加软件源信息sudo yum-config-manager –add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo# 更新 yum 缓存sudo yum makecache fast# 安装 Docker-cesudo yum -y install docker-ce# 启动docker并设置为开机启动(centos7)systemctl start docker.servicesystemctl enable docker.service# 替换docker为国内源echo ‘{“registry-mirrors”: [“https://registry.docker-cn.com”],“live-restore”: true}’ > /etc/docker/daemon.jsonsystemctl restart docker# 安装dokcer-composesudo curl -L https://github.com/docker/compose/releases/download/1.22.0/docker-compose-`uname -s-uname -m` -o /usr/local/bin/docker-composechmod +x /usr/local/bin/docker-compose# 安装命令补全工具yum -y install bash-completioncurl -L https://raw.githubusercontent.com/docker/compose/$(docker-compose version –short)/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose### 安装docker结束 ###Docker化改造改造后目录结构先看一下改造后的项目的结构├─Fame│ │ .env // docker-compose环境参数配置文件│ │ docker-compose.yml // docker-compose文件│ ├─fame-docker│ │ │ fame-front-Dockerfile // fame-front的Dockerfile文件│ │ │ fame-server-Dockerfile // fame-server的Dockerfile文件│ │ │ │ │ ├─fame-admin│ │ │ fame-admin-Dockerfile // fame-admin的Dockerfile文件│ │ │ nginx.conf // fame-admin的nginx服务器配置文件│ │ │ │ │ ├─fame-mysql│ │ │ fame-mysql-Dockerfile // mysql的Dockerfile文件│ │ │ mysqld.cnf // mysql的配置文件mysqld.cnf│ │ │ │ │ └─fame-nginx│ │ nginx-Dockerfile // 整个项目的nginx服务器的Dockerfile文件│ │ nginx.conf // 整个项目的nginx的配置文件│ │ │ ├─fame-admin // 博客管理后台,基于Vue+elementui│ ├─fame-front // 博客前端,基于Nuxt│ └─fame-server // 博客服务端,基于spring-boot为了不破坏原有项目的结构,无论前端还是后端的docker的相关配置文件全部提取出来,单独放在了fame-docker文件夹中。docker-compose.yml放在项目根目录下,直接在根目录运行命令:docker-compose up -d[root@localhost Fame]# docker-compose up -dStarting fame-front … Starting fame-admin … Starting fame-front … doneStarting fame-admin … doneStarting fame-nginx … done就启动项目了,再也不用重复繁琐的步骤!改造后的docker项目结构改造后的docker-compose.yaml文件version: ‘3’services: fame-nginx: container_name: fame-nginx build: context: ./ dockerfile: ./fame-docker/fame-nginx/nginx-Dockerfile ports: - “80:80” volumes: - ./logs/nginx:/var/log/nginx depends_on: - fame-server - fame-admin - fame-front fame-mysql: container_name: fame-mysql build: context: ./ dockerfile: ./fame-docker/fame-mysql/fame-mysql-Dockerfile environment: MYSQL_DATABASE: fame MYSQL_ROOT_PASSWORD: root MYSQL_ROOT_HOST: ‘%’ TZ: Asia/Shanghai expose: - “3306” volumes: - ./mysql/mysql_data:/var/lib/mysql restart: always fame-server: container_name: fame-server restart: always build: context: ./ dockerfile: ./fame-docker/fame-server-Dockerfile working_dir: /app volumes: - ./fame-server:/app - ~/.m2:/root/.m2 - ./logs/fame:/app/log expose: - “9090” command: mvn clean spring-boot:run -Dspring-boot.run.profiles=docker -Dmaven.test.skip=true depends_on: - fame-mysql fame-admin: container_name: fame-admin build: context: ./ dockerfile: ./fame-docker/fame-admin/fame-admin-Dockerfile args: BASE_URL: ${BASE_URL} expose: - “3001” fame-front: container_name: fame-front build: context: ./ dockerfile: ./fame-docker/fame-front-Dockerfile environment: BASE_URL: ${BASE_URL} PROXY_HOST: ${PROXY_HOST} PROXY_PORT: ${PROXY_PORT} expose: - “3000"docker-compose.yml的结构和刚才目录结构大体类似,也是分以下几个部分fame-nginxfame-mysqlfame-serverfame-adminfame-front这个docker-compose.yml中有几个要点fame-mysql和fame-server的restart要设置为always,因为目前Docker-compose是没有一个方案可以解决容器启动的先后的问题的。即使设置了depends_on,那也只是控制容器开始启动的时间,不能控制容器启动完成的时间,所以让fame-mysql和fame-server这两个容器设置restart,防止spring-boot在mysql启动完成之前启动而报错启动失败fame-server,fame-mysql,fame-nginx这三个容器都设置了volumes,把容器里的logs日志文件挂载到宿主机的项目目录里,方便随时看日志文件fame-mysql容器的mysql存储文件也设置了volumes挂载在项目目录里(./mysql/mysql_data:/var/lib/mysql),这个建议大家可以根据实际的情况设置到宿主机的其他目录里,不然不小心删除项目的话那么容器里的数据库数据也都没了几个镜像的Dockerfile大部分都比较简单,这部分就不全部详细介绍了,可以直接去我项目中了解。Docker化过程的困难和解决方法spring-boot双配置切换为了能够让spring-boot能够在开发环境和Docker环境下快速切换,需要将spring-boot的配置文件进行修改└─fame-server … │ └─resources │ │ application-dev.properties │ │ application-docker.properties │ │ application.properties在原有的application.properties基础上增加application-dev.properties和application-docker.properties配置文件,把application.properties里的数据库日志等信息分别放到application-dev.properties和application-docker.properties这两个文件中,实现开发环境和Docker环境的快速切换。# application.properties文件#端口号server.port=9090#mybatismybatis.type-aliases-package=com.zbw.fame.Model#mappermapper.mappers=com.zbw.fame.util.MyMappermapper.not-empty=falsemapper.identity=MYSQL#mailspring.mail.properties.mail.smtp.auth=truespring.mail.properties.mail.smtp.starttls.enable=truespring.mail.properties.mail.smtp.starttls.required=true#默认propertiesspring.profiles.active=dev# application-docker.properties文件#datasourcespring.datasource.driverClassName=com.mysql.jdbc.Driverspring.datasource.url=jdbc:mysql://fame-mysql:3306/fame?useUnicode=true&characterEncoding=utf-8&useSSL=falsespring.datasource.username=rootspring.datasource.password=root#loglogging.level.root=INFOlogging.level.org.springframework.web=INFOlogging.file=log/fame.logapplication-dev.properties的内容和application-docker.properties文件类似,只是根据自己开发环境的情况修改mysql和log配置。动态配置axios的baseUrl地址在fame-admin和fame-front中用了axios插件,用于发起和获取fame-server服务器的请求。在axios要配置服务器url地址baseUrl,那么通常开发环境和Docker环境以及生产环境的url可能都不一样,每次都去修改有点麻烦。(虽然只需要配置两处,但是代码洁癖不允许我硬编码这个配置)。先修改fame-admin(Vue)使其兼容手动部署模式和Docker模式fame-admin是基于Vue CLI 3搭建的,相对于cli 2.0官方把webpack的一些配置文件都封装起来了,所以没有config和build文件夹。不过对应的官网也给了一些设置更加方便的配置参数。在官方文档中提到:只有以 VUE_APP_ 开头的变量会被 webpack.DefinePlugin 静态嵌入到客户端侧的包中。你可以在应用的代码中这样访问它们:console.log(process.env.VUE_APP_SECRET)在构建过程中,process.env.VUE_APP_SECRET 将会被相应的值所取代。在 VUE_APP_SECRET=secret 的情况下,它会被替换为 “sercet”。利用这个特性来设置环境变量来动态的设置Docker模式和手动部署模式的baseUrl的值在fame-admin目录下创建文件server-config.js,编写以下内容const isProd = process.env.NODE_ENV === ‘production’const localhost = ‘http://127.0.0.1:9090/‘const baseUrl = process.env.VUE_APP_API_URL || localhostconst api = isProd ? baseUrl : localhostexport default { isProd, api}那么只要在环境变量中有VUE_APP_API_URL的值,且NODE_ENV === ‘production’,baseUrl就等于VUE_APP_API_URL的值,否则就是localhost的值。接着在axios配置文件中引用该文件设置// fame-admin/src/plugins/http.js…import serverConfig from ‘../../server-config’const Axios = axios.create({ baseURL: serverConfig.api + ‘api/’, …}) …现在只要将docker的环境变量设置一个VUE_APP_API_URL的值就行了,只要在对应的Dockerfile中增加一个步骤就可以了。ENV VUE_APP_API_URL http://xx.xxx.xxx.xxx再修改fame-front(Nuxt)使其兼容手动部署模式和Docker模式同样的,对于用Nuxt搭建fame-front博客前台修改也是类似的思路。在Nuxt的官方文档中写到:Nuxt.js 让你可以配置在客户端和服务端共享的环境变量。例如 (nuxt.config.js):module.exports = { env: { baseUrl: process.env.BASE_URL || ‘http://localhost:3000’ }}以上配置我们创建了一个 baseUrl 环境变量,如果应用设定了 BASE_URL 环境变量,那么 baseUrl 的值等于 BASE_URL 的值,否则其值为 http://localhost:3000。所以我们只要和官方文档说的一样,在nuxt.config.js文件中增加代码就可以了module.exports = { env: { baseUrl: process.env.BASE_URL || ‘http://localhost:3000’ }}接着在server-config.js文件和axios的配置文件fame-front/plugins/http.js以及对应的Dockerfile文件中编写和上面fame-admin部分一样的代码就可以了现在已经把baseUrl的设置从代码的硬编码中解放出来了,但事实上我们只是把这个参数的编码从代码从转移到Dockerfile文件里了,要是想要修改的话也要去这两个文件里查找然后修改,这样也不方便。后面会解决这个问题把所有环境配置统一起来。Nuxt在Docker中无法访问到宿主机ip问题先要说明一点,为什么博客前端要单独去使用的Nuxt而不是和博客后台一样用Vue呢,因为博客前端有SEO的需求的,像Vue这样的对搜索引擎很不友好。所以Nuxt的页面是服务器端渲染(SSR)的这样就产生了问题fame-front的页面在渲染之前必须获取到fame-server服务器中的数据,但是每个docker容器都是互相独立的,其内部想要互相访问只能通过容器名访问。例如容器fame-front想要访问容器fame-server,就设置baseURL = fame-server (fame-server是服务器的容器的container_name)。这样设置之后打开浏览器输入网址:http://xx.xxx.xxx.xx可以成功…,但是随便点击一个链接,就会看到浏览器提示错误无法访问到地址http://fame-server/…vendor.e2feb665ef91f298be86.js:2 GET http://fame-server/api/article/1 net::ERR_CONNECTION_REFUSED这是必然的结果,在容器里http://fame-server/就是服务器…,但是你本地的浏览器当然是不知道http://fame-server/是个什么鬼…,所以就浏览器就报出无法访问的错误。什么?可是刚才不是说Nuxt是服务器渲染的页面吗,怎么又让本地浏览器报这个错误了。原来是因为当通过浏览器链接直接访问的时候,Nuxt的确是从后端渲染了页面再传过来,但是在页面中点击链接的时候是通过Vue-Router跳转的,这时候不在Nuxt的控制范围,而是和Vue一样在浏览器渲染的,这时候就要从浏览器里向服务端获取数据来渲染,浏览器就会报错。如何解决呢这个问题开始的时候一直想要尝试配置Docker容器的网络模式来解决,可是都没有解决。直到后面我看axios文档的时候才注意到axios的代理功能,其本质是解决跨域的问题的,因为只要在axios设置了代理,在服务端渲染的时候就会使用代理的地址,同时在浏览器访问的时候会用baseUrl 的地址,这个特点完美解决我的问题啊。在server-config.js文件里增加以下代码(在nuxt.config.js里获取环境变量里的proxyHost和proxyPort)…const localProxy = { host: ‘127.0.0.1’, port: 9090}const baseProxy = { host: process.env.proxyHost || localProxy.host, port: process.env.proxyPort || localProxy.port}exports.baseProxy = isProd ? baseProxy : localProxy…然后在axios配置文件里增加代码// fame-front/plugins/http.jsconst Axios = axios.create({ proxy: serverConfig.baseProxy …}) …就可以完美的解决问题了。Dockerfile的环境参数统一设置在上文解决动态配置axios地址的部分把baseUrl的设置放在了Dockerfile中,现在就再把Dockerfile中的硬编码提取出来,放到统一的配置文件中。首先在docker-compose.yml文件目录下(即项目跟目录)创建环境文件.env并编写一下内容BASE_URL=http://xx.xxx.xxx.xxxPROXY_HOST=fame-nginxPROXY_PORT=80这个是docker-compose的env_file参数,从文件中获取环境变量,可以为单独的文件路径或列表,如果同目录下有.env文件则会默认读取,也可以自己在docker-compose里设置路径。已经在.env设置了环境变量BASE_URL的值,就能在docker-compose.yml里直接使用了。修改docker-compose.yml的fame-front部分:fame-front: … environment: BASE_URL: ${BASE_URL} PROXY_HOST: ${PROXY_HOST} PROXY_PORT: ${PROXY_PORT} …这样在fame-front的容器里就有对应的BASE_URL,PROXY_HOST,PROXY_PORT环境变量,Nuxt也能够成功获取并设置。不过对于fame-admin容器来说就要稍微复杂一点点了。先来看一下fame-admin容器的Dockerfile文件fame-admin-Dockerfile# build stageFROM node:10.10.0-alpine as build-stage#中间一些操作省略…RUN npm run build# production stageFROM nginx:1.15.3-alpine as production-stageCOPY ./fame-docker/fame-admin/nginx.conf /etc/nginx/conf.d/default.confCOPY –from=build-stage /app/dist /usr/share/nginx/htmlEXPOSE 80CMD [“nginx”, “-g”, “daemon off;"]这里用了多阶段构建容器,如果直接通过docker-compose设置环境变量只会在后面一个阶段生效,但是npm run build是在第一个阶段执行的,所以环境变量不能应用到Vue当中。为了让环境变量在第一阶段就应用,必须要在构建的时候就把变量从docker-compose传到fame-admin-Dockerfile中,然后在Dockerfile中的第一阶段把这个环境变量应用到容器里。下面修改docker-compose.yml的fame-admin部分: fame-admin: … build: context: ./ dockerfile: ./fame-docker/fame-admin/fame-admin-Dockerfile args: BASE_URL: ${BASE_URL} # 这里把环境变量当做ARG传给Dockerfile …然后在fame-admin-Dockerfile的第一阶段增加步骤# build stageFROM node:10.10.0-alpine as build-stageARG BASE_URL # 必须申明这个ARG才能从docker-compose里获取ENV VUE_APP_API_URL $BASE_URL# 以下省略…这样就可以在构建阶段一镜像的时候就把环境变量传入到阶段一的镜像里,让Vue里的变量生效了。总结现在网上很多复杂一点的项目即使用了docker-compose部署,也多少依赖shell脚本来操作,比如复制文件设置环境等,我觉得这样会降低docker-compose的意义。如果都使用了shell脚本,那不如直接不用docker-compose而全用shell来构建和启动镜像。所以在Docker化的过程中虽然遇到一些坎坷,但坚持实现了只用docker-compose部署,以后上线和下线就及其方便了。也希望我的Docker化思路可以给其他项目做一些参考。对比以前恐怖的步骤,现在Fame博客的上线和下线只需要两行命令,真的十分的便捷。docker-compose updocker-compose down源码地址:doodle原文地址:使用Docker部署Spring-Boot+Vue博客系统 ...

September 29, 2018 · 3 min · jiezi

微服务写的最全的一篇文章

今年有人提出了2018年微服务将疯狂至死,可见微服务的争论从未停止过。在这我将自己对微服务的理解整理了一下,希望对大家有所帮助。1.什么是微服务1)一组小的服务(大小没有特别的标准,只要同一团队的工程师理解服务的标识一致即可)2)独立的进程(java的tomcat,nodejs等)3)轻量级的通信(不是soap,是http协议)4)基于业务能力(类似用户服务,商品服务等等)5)独立部署(迭代速度快)6)无集中式管理(无须统一技术栈,可以根据不同的服务或者团队进行灵活选择)ps:微服务的先行者Netflix公司,开源了一些好的微服务框架,后续会有介绍。2.怎么权衡微服务的利于弊利:强模块边界 。(模块化的演化过程:类–>组件/类库(sdk)–>服务(service),方式越来越灵活)可独立部署。技术多样性。弊:分布式复杂性。最终一致性。(各个服务的团队,数据也是分散式治理,会出现不一致的问题)运维复杂性。测试复杂性。3.企业在什么时候考虑引入微服务从生产力和系统的复杂性这两个方面来看。公司一开始的时候,业务复杂性不高,这时候是验证商业模式的时候,业务简单,用单体服务反而生产力很高。随着公司的发展,业务复杂性慢慢提高,这时候就可以采用微服务来提升生产力了。至于这个转化的点,需要团队的架构师来进行各方面衡量,就个人经验而言,团队发展到百人以上,采用微服务就很有必要了。有些架构师是具有微服务架构能力,所以设计系统时就直接设计成了微服务,而不是通过单服务慢慢演化发展成微服务。在这里我并不推荐这种做法,因为一开始对业务领域并不是很了解,并且业务模式还没有得到验证,这时候上微服务风险比较高,很有可能失败。所以建议大家在单服务的应用成熟时,并且对业务领域比较熟悉的时候,如果发现单服务无法适应业务发展时,再考虑微服务的设计和架构。4.微服务的组织架构如上图左边,传统的企业中,团队是按职能划分的。开发一个项目时,会从不同的职能团队找人进行开发,开发完成后,再各自回到自己的职能团队,这种模式实践证明,效率还是比较低的。如上图右边,围绕每个业务线或产品,按服务划分团队。团队成员从架构到运维,形成一个完整的闭环。一直围绕在产品周围,进行不断的迭代。不会像传统的团队一样离开。这样开发效率会比较高。至于这种团队的规模,建议按照亚马逊的两个披萨原则,大概10人左右比较好。5:怎么理解中台战略和微服务中台战略的由来:马云2015年去欧洲的一家公司supersell参观,发现这个公司的创新能力非常强,团队的规模很小,但是开发效率很高。他们就是采用中台战略。马云感触很深,回国后就在集团内部推出了中台战略。简单的理解就是把传统的前后台体系中的后台进行了细分。阿里巴巴提出了大中台小前台的战略。就是强化业务和技术中台,把前端的应用变得更小更灵活。当中台越强大,能力就越强,越能更好的快速响应前台的业务需求。打个比喻,就是土壤越肥沃,越适合生长不同的生物,打造好的生态系统。6:服务分层每个公司的服务分层都不相同,有的公司服务没有分层,有的怎分层很多。目前业界没有统一的标准。下面推荐一个比较容易理解的两层结构。1:基础服务: 比如一个电商网站,商品服务和订单服务就属于基础服务(核心领域服务)。缓存服务,监控服务,消息队列等也属于基础服务(公共服务)2:聚合服务 :例如网关服务就算一种聚合服务(适配服务)。这是一种逻辑划分,不是物理划分,实际设计的东西很多很复杂。7:微服务的技术架构体系下图是一个成型的互联网微服务的架构体系:1:接入层 负载均衡作用,运维团队负责2:网关层 反向路由,安全验证,限流等3:业务服务层 基础服务和领域服务4:支撑服务层5:平台服务6:基础设施层 运维团队负责。(或者阿里云)8:微服务的服务发现的三种方式第一种:如下图所示,传统的服务发现(大部分公司的做法)。服务上线后,通知运维,申请域名,配置路由。调用方通过dns域名解析,经过负载均衡路由,进行服务访问。缺点: LB的单点风险,服务穿透LB,性能也不是太好第二种:也叫客户端发现方式。如下图所示。通过服务注册的方式,服务提供者先注册服务。消费者通过注册中心获取相应服务。并且把LB的功能移动到了消费者的进程内,消费者根据自身路由去获取相应服务。优点是,没有了LB单点问题,也没有了LB的中间一跳,性能也比较好。但是这种方式有一个非常明显的缺点就是具有非常强的耦合性。针对不同的语言,每个服务的客户端都得实现一套服务发现的功能。第三种:也叫服务端发现方式,如下图所示。和第二种很相似。但是LB功能独立进程单独部署,所以解决了客户端多语言开发的问题。唯一的缺点就是运维成比较高,每个节点都得部署一个LB的代理,例如nginx。9.微服务网关网关就好比一个公司的门卫。屏蔽内部细节,统一对外服务接口。下图是一个网关所处位置的示例图。10.Netflix Zuul网关介绍核心就是一个servlet,通过filter机制实现的。主要分为三类过滤器:前置过滤器,过滤器和后置过滤器。主要特色是,这些过滤器可以动态插拔,就是如果需要增加减少过滤器,可以不用重启,直接生效。原理就是:通过一个db维护过滤器(上图蓝色部分),如果增加过滤器,就将新过滤器编译完成后push到db中,有线程会定期扫描db,发现新的过滤器后,会上传到网关的相应文件目录下,并通知过滤器loader进行加载相应的过滤器。整个网关调用的流程上图从左变http Request开始经过三类过滤器,最终到最右边的Http Response,这就是Zull网关的整个调用流程。在此我向大家推荐一个架构学习交流群。交流学习群号:478030634 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多11:微服务的路由发现体系整个微服务的路由发现体系,一般由服务注册中心和网关两部分组成。以NetFlix为例子,Eureka和Zull这两个组件支撑了netFlix整个的路由发现体系。如下图所示,首先外部请求发送到网关,网关去服务注册中心获取相应的服务,进行调用。其次内部服务间的调用,也通过服务注册中心进行的12.微服务配置中心目前大部分公司都是把配置写到配置文件中,遇到修改配置的情况,成本很高。并且没有修改配置的记录,出问题很难追溯。配置中心就接解决了以上的问题。可配置内容:数据库连接,业务参数等等配置中心就是一个web服务,配置人员通过后台页面修改配置,各个服务就会得到新的配置参数。实现方式主要有两种,一种是push,另一种是pull。两张方式各有优缺点。push实时性较好,但是遇到网络抖动,会丢失消息。pull不会丢失消息但是实时性差一些。大家可以同时两种方式使用,实现一个比较好的效果。如下图所示,这是一个国内知名互联网公司的配置中心架构图。开源地址:http://github.com/ctripcorp/a…13:RPC遇到了REST内部一些核心服务,性能要求比较高的可以采用RPC,对外服务的一般可以采用rest。14:服务框架和治理微服务很多的时候,就需要有治理了。一个好的微服务框架一般分为以下14个部分。如下图所示。这就是开篇所说的,微服务涉及的东西很多,有些初创公司和业务不成熟的产品是不太适合的,成本比较高。目前国内比较好的微服务框架就是阿里巴巴的DUBBO了,国外的就是spring cloud,大家可以去研究一下.15:监控体系监控是微服务治理的重要环节。一般分为以下四层。如下图所示。监控的内容分为五个部分:日志监控,Metrics监控(服务调用情况),调用链监控,告警系统和健康检查。日志监控,国内常用的就是ELK+KAFKA来实现。健康检查和Metrics,像spring boot会自带。Nagios也是一个很好的开源监控框架。16:Trace调用链监控调用链监控是用来追踪微服务之前依赖的路径和问题定位。例如阿里的鹰眼系统。主要原理就是子节点会记录父节点的id信息。下图是目前比较流行的调用链监控框架。17:微服务的限流熔断假设服务A依赖服务B和服务C,而B服务和C服务有可能继续依赖其他的服务,继续下去会使得调用链路过长。如果在A的链路上某个或几个被调用的子服务不可用或延迟较高,则会导致调用A服务的请求被堵住,堵住的请求会消耗占用掉系统的线程、io等资源,当该类请求越来越多,占用的计算机资源越来越多的时候,会导致系统瓶颈出现,造成其他的请求同样不可用,最终导致业务系统崩溃。一般情况对于服务依赖的保护主要有两种方式:熔断和限流。目前最流行的就是Hystrix的熔断框架。下图是Hystrix的断路器原理图:限流方式可以采用zuul的API限流方法。18.Docker 容器部署技术&持续交付流水线随着微服务的流行,容器技术也相应的被大家重视起来。容器技术主要解决了以下两个问题:1:环境一致性问题。例如java的jar/war包部署会依赖于环境的问题(操着系统的版本,jdk版本问题)。2:镜像部署问题。例如java,rubby,nodejs等等的发布系统是不一样的,每个环境都得很麻烦的部署一遍,采用docker镜像,就屏蔽了这类问题。下图是Docker容器部署的一个完整过程。更重要的是,拥有如此多服务的集群环境迁移、复制也非常轻松,只需选择好各服务对应的Docker服务镜像、配置好相互之间访问地址就能很快搭建出一份完全一样的新集群。19.容器调度和发布体系目前基于容器的调度平台有Kubernetes,mesos,omega。下图是mesos的一个简单架构示意图。下图是一个完整的容器发布体系 大家觉得文章对你还是有一点点帮助的,大家可以点击下方二维码进行关注。 《乐趣区》 公众号聊的不仅仅是Java技术知识,还有面试等干货,后期还有大量架构干货。大家一起关注吧!关注烂猪皮,你会了解的更多…………..

September 27, 2018 · 1 min · jiezi

docker pull 翻墙下载镜像

我们一般通过设置http_proxy环境变量,使得http请求,可以走我们设置的proxy,(一些go get镜像无法下载可以这么用),但是对于docker pull命令是不生效的,因为systemd引导启动的service默认不会读取这些变量,所以我们可以通过在service文件中加入环境变量解决:修改systemd service文件docker service文件/usr/lib/systemd/system/docker.service:[Service] Environment=“HTTP_PROXY=http://proxy.example.com:80/” “HTTPS_PROXY=http://proxy.example.com:80/““NO_PROXY=localhost,127.0.0.1,docker-registry.somecorporation.com"其中NO_PROXY变量指的是那些http请求不走代理。重启docker生效systemctl daemon-reloadsystemctl restart dockerTIPS: polipo 可以将socks5协议转换成http代理。参考资料https://docs.docker.com/config/daemon/systemd/#httphttps-proxy原文地址:http://silenceper.com/blog/20…

September 24, 2018 · 1 min · jiezi

使用Docker快速部署ELK分析Nginx日志实践(二)

Kibana汉化使用中文界面实践一、背景笔者在上一篇文章使用Docker快速部署ELK分析Nginx日志实践当中有提到如何快速搭建ELK分析Nginx日志,但是这只是第一步,后面还有很多仪表盘需要配置,而对于大部分人来说,英文并不是那么好,但Kibana都是英文界面,这就阻碍了笔者熟悉Kibana的一些操作;所以笔者思考能不能将其汉化,在搜索引擎中找到了一些文章,发现汉化相对来说成本还算比较低,因此进行了一番实践,整个操作流程即便是将前人的汉化包拿过来使用,但使用的过程汉化包的作者并没有过多的讲解,本文主要是讲解如何使用汉化包以及操作过程的记录。笔者上一篇文章使用Docker快速部署ELK分析Nginx日志实践URL地址:https://segmentfault.com/a/11…二、操作概述汉化包下载运行环境安装汉化效果演示三、汉化包下载笔者所使用的汉化包项目名称为Kibana_Hanization,在Github上进行了开源,URL地址如下https://github.com/anbai-inc/Kibana_Hanization在上一篇文章当中笔者已经将/Users/song/dockerFile/挂载在容器的/data当中,因此可以直接在宿主机中通过git拉取汉化包,然后去容器里面运行它,参考命令如下cd /Users/song/dockerFile/ && git clone https://github.com/anbai-inc/Kibana_Hanization.git四、运行环境安装安装汉化包,需要完成三个步骤,首先需要有执行汉化包里面工具的Python2.7环境,然后需要找到Kibana的安装目录,最后才能执行安装,具体操作如下4.1 安装Python2.7笔者直接运行汉化包的时候发现此汉化工具依赖于Python2.7,而ELK中默认安装的是Python3,因此笔者需要先安装Python2.7的运行环境,操作如下首先需要拉取Python仓库地址apt update然后执行安装,参考命令如下apt install python2.74.2 查找安装位置安装好Python的运行环境之后,笔者还需要找到kibana的安装位置,参考命令如下所示find / -iname kibana命令执行后返回的结果/opt/logstash/x-pack/modules/azure/configuration/kibana/opt/logstash/x-pack/modules/arcsight/configuration/kibana/opt/logstash/modules/netflow/configuration/kibana/opt/logstash/modules/fb_apache/configuration/kibana/opt/kibana/opt/kibana/src/core_plugins/kibana/opt/kibana/node_modules/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/opt/kibana/node_modules/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx/kibana/opt/kibana/node_modules/x-pack/plugins/monitoring/server/lib/kibana/opt/kibana/node_modules/x-pack/plugins/monitoring/server/lib/metrics/kibana/opt/kibana/node_modules/x-pack/plugins/monitoring/server/routes/api/v1/kibana/opt/kibana/node_modules/x-pack/plugins/monitoring/public/views/kibana/opt/kibana/node_modules/x-pack/plugins/monitoring/public/components/kibana/opt/kibana/node_modules/x-pack/plugins/monitoring/public/directives/kibana/opt/kibana/node_modules/@kbn/pm/src/utils/fixtures/kibana/opt/kibana/bin/kibana/etc/logrotate.d/kibana/etc/init.d/kibana根据返回结果和以往的经验,大致猜测出安装位置在/opt/kibana下,在得到安装目录之后,现在笔者需要进入此前在宿主机通过git下载的汉化包目录,因为运行elk容器的时候已经将宿主机目录挂载进去,因此容器中可以进入,参考吗命令如下cd /data/Kibana_Hanization4.2 汉化包安装执行汉化命令python2.7 main.py /opt/kibana/返回结果文件[/opt/kibana/optimize/bundles/kibana.bundle.js]已翻译。文件[/opt/kibana/optimize/bundles/commons.bundle.js]已翻译。文件[/opt/kibana/optimize/bundles/login.bundle.js]已翻译。文件[/opt/kibana/optimize/bundles/ml.bundle.js]已翻译。文件[/opt/kibana/optimize/bundles/monitoring.bundle.js]已翻译。文件[/opt/kibana/optimize/bundles/timelion.bundle.js]已翻译。文件[/opt/kibana/optimize/bundles/vendors.bundle.js]已翻译。文件[/opt/kibana/optimize/bundles/apm.bundle.js]已翻译。文件[/opt/kibana/src/core_plugins/kibana/ui_setting_defaults.js]已翻译。文件[/opt/kibana/src/core_plugins/kibana/index.js]已翻译。文件[/opt/kibana/src/core_plugins/kibana/translations/en.json]已翻译。文件[/opt/kibana/src/core_plugins/kibana/server/tutorials/docker_metrics/index.js]已翻译。文件[/opt/kibana/src/core_plugins/kibana/server/tutorials/netflow/elastic_cloud.js]已翻译。文件[/opt/kibana/src/core_plugins/kibana/server/tutorials/netflow/on_prem_elastic_cloud.js]已翻译。文件[/opt/kibana/src/core_plugins/kibana/server/tutorials/netflow/on_prem.js]已翻译。文件[/opt/kibana/src/core_plugins/kibana/server/tutorials/netflow/common_instructions.js]已翻译。文件[/opt/kibana/src/core_plugins/kibana/server/tutorials/kubernetes_metrics/index.js]已翻译。文件[/opt/kibana/src/core_plugins/kibana/server/tutorials/apache_metrics/index.js]已翻译。文件[/opt/kibana/src/core_plugins/kibana/server/tutorials/redis_metrics/index.js]已翻译。文件[/opt/kibana/src/core_plugins/kibana/server/tutorials/apm/apm_server_instructions.js]已翻译。文件[/opt/kibana/src/core_plugins/kibana/server/tutorials/apm/apm_client_instructions.js]已翻译。文件[/opt/kibana/src/core_plugins/kibana/server/tutorials/nginx_metrics/index.js]已翻译。文件[/opt/kibana/src/core_plugins/kibana/server/tutorials/system_metrics/index.js]已翻译。文件[/opt/kibana/src/core_plugins/kibana/server/tutorials/system_logs/index.js]已翻译。文件[/opt/kibana/src/core_plugins/kibana/server/tutorials/apache_logs/index.js]已翻译。文件[/opt/kibana/src/core_plugins/kibana/server/tutorials/nginx_logs/index.js]已翻译。文件[/opt/kibana/src/core_plugins/kibana/server/tutorials/redis_logs/index.js]已翻译。文件[/opt/kibana/src/core_plugins/kibana/server/tutorials/mysql_metrics/index.js]已翻译。文件[/opt/kibana/src/core_plugins/kibana/server/tutorials/mysql_logs/index.js]已翻译。文件[/opt/kibana/src/core_plugins/kibana/common/tutorials/filebeat_instructions.js]已翻译。文件[/opt/kibana/src/core_plugins/kibana/common/tutorials/metricbeat_instructions.js]已翻译。文件[/opt/kibana/src/core_plugins/kibana/public/dashboard/index.js]已翻译。文件[/opt/kibana/src/core_plugins/timelion/index.js]已翻译。文件[/opt/kibana/src/core_plugins/kbn_vislib_vis_types/public/line.js]已翻译。文件[/opt/kibana/src/core_plugins/kbn_vislib_vis_types/public/area.js]已翻译。文件[/opt/kibana/src/core_plugins/kbn_vislib_vis_types/public/heatmap.js]已翻译。文件[/opt/kibana/src/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js]已翻译。文件[/opt/kibana/src/core_plugins/kbn_vislib_vis_types/public/histogram.js]已翻译。文件[/opt/kibana/src/ui/public/chrome/directives/global_nav/global_nav.js]已翻译。恭喜,Kibana汉化完成!笔者执行这条命令时间大约在10秒钟左右。五、汉化效果演示经过上一步操作,已经完成了汉化包的安装,现在笔者进入Kibana的主页来验证汉化的效果,Kibana主页的URL地址如下http://localhost:5601/app/kibana#/home?_g=()但在实际汉化后发现并没有完全汉化,笔者所使用的ELK版本为6.4.0,效果如下图所示而汉化包中介绍的汉化效果效果却如下图所示笔者猜测可能是自己使用的ELK版本比较新,而汉化包还没用跟上节奏所导致,不过效果已经很棒了;笔者接着又打开了几个页面,发现汉化效果大都在80%左右,视图创建URL地址如下http://localhost:5601/app/kibana#/visualize/new?_g=()在浏览器中打开视图创建页面后,展现汉化如下图所示作者:汤青松微信:songboy8888日期:2018-08-31

September 1, 2018 · 1 min · jiezi

Kubernetes 简介

什么是DockerDocker不是虚拟机。 在很多的网络教案中喜欢将Docker与虚拟机进行类比,这种类比用于理解Docker的优势有着不错的作用,因为Docker与虚拟机有着相同的优势。但是从技术而言,虚拟机技术则是对硬件层的虚拟化,而Docker是一种进程隔离技术。简单的说,我们可以在宿主机(Host Machine)使用 ps aux 看到使用Docker启动的进程。这意味着Docker中的程序实际上是跑在宿主操作系统中。这是我不赞成将Docker与虚拟机进行类比的原因。要去理解Docker,最避不开的是Namespace 与Cgroups。Namespace 是Linux中对进程之间进行隔离保护的技术。通过Namespace技术,保证了不同Docker Container中看到的内容不一样,这也是Docker的基础。cgroups(Control Groups)最初叫Process Container,由Google工程师(Paul Menage和Rohit Seth)于2006年提出,后来因为Container有多重含义容易引起误解,就在2007年更名为Control Groups,并被整合进Linux内核。顾名思义就是把进程放到一个组里面统一加以控制 。本质上,Cgroups 是一种对进程资源控制(比如可访问内存,CPU时间使用)的技术手段。 这里我们可以更加清楚的明白,Docker只是一个基于操作系统提供的虚拟化能力的一个上层应用,其核心功能都是由内核实现。这也是与虚拟机最大的不同点。什么是KubernetesKubernetes 是一个由Google 主导开发的开源容器编排平台,通过数年的发展已经成为了容器编排的事实标准。各大云平台也都提供了完善的Kubernetes功能,我们可以在Kubernetes的源码中看到目前适配的云平台。在Kubernetes的世界里,我们不在关心具体的服务器细节,我们看到的是一个一个CPU,一条一条内存,一块一块的磁盘摆在眼前,服务器已经被完全的抽象为计算资源。应用服务器的无状态化带来的最大好处是扩容的便利性,我们可以借助云平台,在数分钟内完成百倍的吞吐量扩展。另一方面,对于应用开发者,所有的一切都在kubectl 这一个工具中。Kubernetes 基础服务0. MasterMaster 管理着整个集群的状态与访问。1. 节点节点(Node)代表着一台物理实例。所有集群中的容器都运行在节点中。一旦某一个节点发生故障,则运行在该节点的容器会自动的迁移到其他节点(注意,迁移的过程是销毁重建。事实上为了状态的一致性,在Kubernetes中没有容器的停止与重启的概念)。在集群运行中,我们可以添加/减少节点。在节点新增之后,2. 网络在集群(Cluster)内部,Kubernetes使用虚拟网络层进行网络通讯。这使得每一个容器都有一个自己独立的ip。我们可以从任何一个容器通过这个ip访问到另一个容器内部。对于Web应用,我们经常需要部署多个实例用于负载均衡,在Kubernetes,可以通过Service服务非常快速的创建一个内部负载均衡(ELB)。对于每一个Service,仍然会分配一个ip,使得我们可以从一个容器去访问另外一组容器而无需任何额外的程序(例如Haproxy,Nginx)支持。在虚拟网络之上,Kubernetes还提供了针对虚拟网络的DNS系统,我们只需要按照特定的规则就可以访问不同的Service,连ip都不需要知晓。虚拟网络完全的避开了对于物理网络细节(例如Ip 地址,端口号)的依赖,使得我们可以通过配置得到一个完全一致的集群环境。3. 存储我们永远不应该在容器中存储任何需要持久化存储的数据(Mysql,Redis),因为容器会崩溃重建,故障迁移等原因造成自动重新部署,这些动作都会造成数据的丢失。如果在容器中需要使用持久化存储,我们需要使用 Persistent Volume 服务。该服务通过将外部的持久化存储系统挂载到容器中进行使用。对于一块磁盘,可以挂载一个允许写操作的卷到一个容器,以及挂载多个只读权限给多个容器。4. 配置不建议将容器中应用程序的配置文件在构建镜像(Docker Image)的时候打包进去,这会造成配置文件的变更需要重新构建。在Kubernetes中,提供了ConfigMap服务进行配置文件的管理。我们通过会通过ConfigMap来构建应用程序的配置管理,然后将其挂载到容器中使用。这可以非常容器的让我们的应用程序跑在不同的环境中。5. 调度当我们向集群新增加一个(些)容器的时候,集群会自动将容器部署到合适的节点。集群会根据不同节点(Node)的状态(节点状态,节点资源状态)来进行规划,并且自动部署。在新增容器的时候,我们还可以指定对应的Node Label将不同的服务部署到不同的物理实例中,以实现服务的物理隔离。Kubernetes 核心概念1. PodPod 是一个或多个Docker容器的组合,它们之间具有共享的存储与网络。Pod也是Kubernetes的最小单位,Pod中的容器会保证永远运行于同一个节点。比如对于PHP-FPM应用而言,需要PHP-FPM进程与对应的Web Server(Nginx,Apache)。基于Docker最小部署原则,我们会将PHP-FPM与Web Server分为两个镜像。我们可以在一个Pod中同时部署PHP-FPM镜像与Web Server进程,并且它们之间仍然可以通过fastcgi的方式进行通讯。而Kubernetes又保证了这些容器具备相同的生命周期。我们将整体理解为一个应用程序即可。2. Development部署(Development)是一个针对如何管理Pod的工具。通过Development我们可以快速地创建多个Pod副本,并且支持滚动热更新,从而实现了应用程序的热更新。我们可以简单的将部署理解为应用的多服务器管理技术手段,在物理机时代,有ansible这样的工具来进行批量部署。3. Service我们已经使用部署将我们的应用部署在了多台节点中,尽管Kubernetes已经提供了虚拟IP来让我们进行不同容器之间的通讯,但是这个IP是不稳定的,因为我们的节点会进行故障迁移,宕机,从而可能更新IP。那么我们如何访问这些服务? 答案就是Service。Service将一些Pod关联在一起,并且设定一些访问策略(比如端口映射),并且允许设定一个固定的ip。当我们尝试去访问这些服务的时候,我们只需要访问Service的ip与端口(映射之后的Service端口)就可以访问这些服务,并且Service本身可以进行负载均衡与健康检查。这意味着Service是一个内部负载均衡器(ELB)。4. IngressIngress本质上和Service一样,也是一种流量代理的概念,但是与Service不一样的是:a. Service是4层代理,Ingress是7层代理(Http)。b. Service是服务的集群内部访问方法,Ingress则是通过一组规则来控制从外部系统(internet)到多个服务的访问方法。我们可以设定不同的域名,不同的Http url path 路由到不同的后端服务(Backend Service)。 因此,Ingress代表着流量入口和负载均衡(LB)的作用。5. StatefulSet从概念上,StatefulSet与Development是非常的相似,但是不同点是:Development是对无状态Pod的管理(比如Http Server),而StatefulSet则是对有状态的Pod进行管理,比如(数据库 MySQL),同时会遵循固定的顺序进行启动或销毁容器。通常地,StatefulSet服务都会搭配持久化存储服务一起工作,我们会将数据库的数据目录指向持久化存储中,这保证了容器在销毁、重建之后数据仍然不会丢失。6. DaemonSetDaemon Set是一种独特的部署方式,它保证了容器会运行在所有指定的节点中,并且保证在节点生命周期内一直存活。这通常适用于一些节点监控程序或采集程序。比如fluentd 或 logstash7. JobJob是一种一次性工作程序的部署方式。它会在所有指定的Pod运行完之后结束生命周期。比如我们可以使用Job来运行数据库迁移程序。8. CronJob从名称可知,CronJob是计划任务的方式来运行程序,并且使用Cron语法来描述间隔周期,但是CronJob会有一些需要注意的地方:a. CronJob是分布式的,它不会保证运行在某一个确定的节点中,除非在YAML中指定。b. CronJob可能因为调度的问题,造成运行多次,这需要我们保证CronJob程序的幂等性。 参考资料http://man7.org/linux/man-pag…https://www.kernel.org/doc/Do…

August 30, 2018 · 1 min · jiezi