暂时未有相关云产品技术能力~
小程序结合Jenkins实现CICD在将公司前后端大多数项目都接入了 CI 流程后,发布效率快的一批。但是我最近发现我司在微信小程序这的发布,居然还是手动??? 程序员天生就对重复的事情敏感,这些事情得想个法弄成自动的。但是在微信小程序出现,上传体验版/生成开发版都需要利用微信开发者工具,最开始是依赖于人手动去点 ide 上的上传按钮,再然后就是,微信开放出了命令行调用接口之前看了一些利用 Mac Os + Jenkins 做 CI,这个方案并不能够适用所有项目组,我们重点还是希望通过服务器进行 CI,于是就去翻找小程序文档以及网上文章, 天无绝人之路,发现小程序的文档多了 CI 这一项miniprogram-ci 从 1.0.28 开始支持第三方平台开发的上传和预览,调用方式与普通开发模式无异整理目录结构因为 miniprogram-ci 需要依赖于 npm 安装,但是又不需要与我们项目的包相关,所以就有了如下结构npm install miniprogram-ci获取小程序代码上传秘钥在公众平台的开发管理 -> 开发设置中,需要先将秘钥下载下来,我们可以写入到项目的根目录的 ci-private.key 文件中,加入版本管理,因为我们有 IP 白名单,所以泄露也不打紧配置本次版本更新内容我们将配置放入到根目录中,方便自动更新的时候获取version.config.json{ "version":"1.1.5", "versionDesc":"Test Jenkins" }CI程序编写start.jsconst ci = require('miniprogram-ci'); const fs = require('fs'); /* 项目配置 */ const projectConfig = require('./project.config.json'); // 就是小程序的配置文件 const versionConfig = require('./version.config.json'); // new ci实例 const project = new ci.Project({ appid: projectConfig.appid, type: 'miniProgram', projectPath: projectConfig.miniprogramRoot, privateKeyPath: './ci-private.key', ignores: ['node_modules/**/*'], }); /** 上传 */ async function upload({version = '0.0.0', versionDesc ='test'}) { await ci.upload({ project, version, desc: versionDesc, setting: { es7: true, minify: true, autoPrefixWXSS: true }, onProgressUpdate: console.log, }) } /** 入口函数 */ async function init() { // 上传 await upload(versionConfig); } init();测试能否上传成功node start.js这样就是可以成功了,然后我们利用 Jenkins 让我提交代码时自动发布体验版接入Jenkinsgit秘钥等凭据添加安装钉钉通知插件、NodeJs环境插件配置钉钉机器人、Node别名创建流水线项目项目基本配置 webhook、构建记录滚动配置等编写Jenkinsfilepipeline { agent any // 环境变量 environment { GIT_ADDRESS = 'git@xxxx/wechat-blog.git' BRANCH_NAME = 'master' } stages { // 拉取git代码 stage('git pull') { steps { git branch: "${BRANCH_NAME}", credentialsId: '1', url: "${GIT_ADDRESS}" } } // 构建 stage('build') { steps { nodejs('nodejs') { sh "npm install" sh "node start.js" } } } } post { success { dingtalk ( robot: '58f10219-2cd3-4de7-a1af-f85f4010c10a', type: 'MARKDOWN', title: "水商城构建通知 - $BRANCH_NAME", text: [ '# 水商城构建通知 - $BRANCH_NAME', '', '---', "- 任务: ${env.BUILD_NUMBER}", '- 状态: <font color=blue>构建成功</font>', ] ) } failure { dingtalk ( robot: '58f10219-2cd3-4de7-a1af-f85f4010c10a', type: 'MARKDOWN', title: "构建通知 - $BRANCH_NAME", text: [ '# 构建通知 - $BRANCH_NAME', '', '---', "- 任务: ${env.BUILD_NUMBER}", '- 状态: <font color=red>构建失败</font>', ] ) } } }Git提交测试钉钉通知成功注意如果上传失败,要记得去增加 IP白名单
laravel实现利用RabbitMQ实现MQTT即时通讯有时候我们的项目中会用到即时通讯功能,比如电商系统中的客服聊天功能,还有在支付过程中,当用户支付成功后,第三方支付服务会回调我们的回调接口,此时我们需要通知前端支付成功。而 RabbitMQ 可以很方便的实现即时通讯功能,如果你的业务只是少量地方使用即时通信,需要一个简易的消息系统,你可以直接考虑 MQ 的实现, MQ 有很高的吞吐率,具有持久化,还可以横向扩展,总之还不错,用就完了,奥利给!本文需要安装好 rabbitMQ 和 laravel ,没弄好环境的看我之前的文章 php laravel5.5使用rabbitmq消息队列MQTT协议MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的轻量级通讯协议,该协议构建于TCP/IP协议上。 MQTT 最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。MQTT相关概念实际上还是 MQ 的那些东西,主要看 MQ 有没有实现 MQTT 模型,懂的随便看看,不懂的先去理解 MQPublisher(发布者):消息的发出者,负责发送消息。Subscriber(订阅者):消息的订阅者,负责接收并处理消息。Broker(代理):消息代理,位于消息发布者和订阅者之间,各类支持MQTT协议的消息中间件都可以充当。Topic(主题):可以理解为消息队列中的路由,订阅者订阅了主题之后,就可以收到发送到该主题的消息。Payload(负载);可以理解为发送消息的内容。QoS(消息质量):全称Quality of Service,即消息的发送质量,主要有QoS 0、QoS 1、QoS 2三个等级,下面分别介绍下:QoS 0(Almost Once):至多一次,只发送一次,会发生消息丢失或重复;QoS 1(Atleast Once):至少一次,确保消息到达,但消息重复可能会发生;QoS 2(Exactly Once):只有一次,确保消息只到达一次。RabbitMQ启用MQTT功能我们是采用 docker 安装的,直接进入容器一顿操作就行docker exec -it rabbitmq bash rabbitmq-plugins enable rabbitmq_mqtt开启成功后,查看管理控制台,我们可以发现 MQTT 服务运行在 1883 端口上了。MQTT客户端我们可以使用 MQTT 客户端来测试 MQTT 的即时通讯功能,这里使用的是 MQTTBox 这个客户端工具。首先下载并安装好 MQTTBox ,下载地址:http://workswithweb.com/mqttbox.html点击 Create MQTT Client 按钮来创建一个 MQTT 客户端;接下来对 MQTT 客户端进行配置,主要是配置好协议端口、连接用户名密码和QoS即可, 注意 Protocol 是 mqtt/tcp然后我们利用这个工具测试一下发布和订阅消息是否可用,一端向 TopicA 发送消息,另一端订阅 TopicA可用看到效果已经出现了,那么我们如何让前端来订阅呢?前端实现即时通讯我们通过 html+javascript 实现一个简单的聊天功能,由于 RabbitMQ 与 Web端 交互底层使用的是 WebSocket ,所以我们需要开启 RabbitMQ 的 MQTT WEB 支持,使用如下命令开启即可docker exec -it rabbitmq bash rabbitmq-plugins enable rabbitmq_web_mqtt开启成功后,查看管理控制台,我们可以发现 MQTT 的 WEB 服务运行在 15675 端口上了;WEB端 与 MQTT 服务进行通讯需要使用一个叫 MQTT.js 的库,项目地址:https://github.com/mqttjs/MQTT.js实现的功能非常简单,一个单聊功能,需要注意的是配置好 MQTT 服务的访问地址为:ws://localhost:15675/ws<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div> <label>目标Topic:<input id="targetTopicInput" type="text"></label><br> <label>发送消息:<input id="messageInput" type="text"></label><br> <button onclick="sendMessage()">发送</button> <button onclick="clearMessage()">清空</button> <div id="messageDiv"></div> </div> </body> <script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script> <script> //RabbitMQ的web-mqtt连接地址 const url = 'ws://ip:15675/ws'; //获取订阅的topic const topic = getQueryString("topic"); //连接到消息队列 let client = mqtt.connect(url); client.on('connect', function () { //连接成功后订阅topic client.subscribe(topic, function (err) { if (!err) { showMessage("订阅topic:" + topic + "成功!"); } }); }); //获取订阅topic中的消息 client.on('message', function (topic, message) { showMessage("收到消息:" + message.toString()); }); //发送消息 function sendMessage() { let targetTopic = document.getElementById("targetTopicInput").value; let message = document.getElementById("messageInput").value; //向目标topic中发送消息 client.publish(targetTopic, message); showMessage("发送消息给" + targetTopic + "的消息:" + message); } //从URL中获取参数 function getQueryString(name) { let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i"); let r = window.location.search.substr(1).match(reg); if (r != null) { return decodeURIComponent(r[2]); } return null; } //在消息列表中展示消息 function showMessage(message) { let messageDiv = document.getElementById("messageDiv"); let messageEle = document.createElement("div"); messageEle.innerText = message; messageDiv.appendChild(messageEle); } //清空消息列表 function clearMessage() { let messageDiv = document.getElementById("messageDiv"); messageDiv.innerHTML = ""; } </script> </html>在Laravel中使用需要保证 laravel 和 rabbitmq 已经可以正常生产和发布消息了,保证没问题再进行以下操作安装mqtt包composer require salmanzafar/laravel-mqttapp.php/* * Application Service Providers... */ Salman\Mqtt\MqttServiceProvider::class,MqttService<?php namespace App\Service; use Illuminate\Support\Facades\Auth; use Salman\Mqtt\MqttClass\Mqtt; class MqttService { public static function SendMsgViaMqtt($topic, $message) { $mqtt = new Mqtt(); $client_id = Auth::user()->id ?? 0; $output = $mqtt->ConnectAndPublish($topic, $message, $client_id); if ($output === true) { return "published"; } return "Failed"; } }laravel生产消息Route::get('/pub', function () { $data = [ 'name' => 'zhangsan', 'age' => 18 ]; $res = \App\Service\MqttService::SendMsgViaMqtt('topicA', json_encode($data)); dump($res); });请求路由测试注意:通过url的queryString进行topic订阅总结消息中间件应用越来越广泛,不仅可以实现可靠的异步通信,还可以实现即时通讯,掌握一个消息中间件还是很有必要的。像普通的订单下了给后台一个推送等等,都可以选择采用 MQ 实现,方便好用!奥利给!!
UUID和雪花(Snowflake)算法该如何选择?UUID 和 Snowflake 都可以生成唯一标识,在分布式系统中可以说是必备利器,那么我们该如何对不同的场景进行不同算法的选择呢,UUID 简单无序十分适合生成 requestID, Snowflake 里面包含时间序列等,可以用于排序,效率都还可以,本文详细介绍了我们选择的使用不同算法的原因,两种算法不同维度的对比。数据库的主键要如何选择?数据库中的每一条记录都需要有一个唯一的标识,依据数据库的第二范式,数据库中每一个表中都需要有一个唯一的主键,其他数据元素和主键一一对应。那么关于主键的选择就成为一个关键点了,一般来讲,你有两种选择方式:使用业务字段作为主键,比如说对于用户表来说,可以使用手机号,email 或者身份证号作为主键。使用生成的唯一 ID 作为主键。不过对于大部分场景来说,第一种选择并不适用,比如像评论表你就很难找到一个业务字段作为主键,因为在评论表中,你很难找到一个字段唯一标识一条评论。而对于用户表来说,我们需要考虑的是作为主键的业务字段是否能够唯一标识一个人,一个人可以有多个 email 和手机号,一旦出现变更 email 或者手机号的情况,就需要变更所有引用的外键信息,所以使用 email 或者手机作为主键是不合适的。身份证号码确实是用户的唯一标识,但是由于它的隐私属性,并不是一个用户系统的必须属性,你想想,你的系统如果没有要求做实名认证,那么肯定不会要求用户填写身份证号码的。并且已有的身份证号码是会变更的,比如在 1999 年时身份证号码就从 15 位变更为 18 位,但是主键一旦变更,以这个主键为外键的表也都要随之变更,这个工作量是巨大的。因此,我更倾向于使用生成的ID作为数据库的主键。不单单是因为它的唯一性,更是因为一旦生成就不会变更,可以随意引用。在单库单表的场景下,我们可以使用数据库的自增字段作为 ID,因为这样最简单,对于开发人员来说也是透明的。但是当数据库分库分表后,使用自增字段就无法保证 ID 的全局唯一性了。想象一下,当我们分库分表之后,同一个逻辑表的数据被分布到多个库中,这时如果使用数据库自增字段作为主键,那么只能保证在这个库中是唯一的,无法保证全局的唯一性。那么假如你来设计用户系统的时候,使用自增 ID 作为用户 ID,就可能出现两个用户有两个相同的 ID,这是不可接受的,那么你要怎么做呢?我建议你搭建发号器服务来生成全局唯一的 ID。UUID与Snowflake对比从我历年所经历的项目中,我主要使用的是变种的 Snowflake 算法来生成业务需要的 ID 的,本讲的重点,也是运用它去解决 ID 全局唯一性的问题。搞懂这个算法,知道它是怎么实现的,就足够你应用它来设计一套分布式发号器了,不过你可能会说了:“那你提全局唯一性,怎么不提 UUID 呢?”没错,UUID(Universally Unique Identifier,通用唯一标识码)不依赖于任何第三方系统,所以在性能和可用性上都比较好,我一般会使用它生成 Request ID 来标记单次请求,但是如果用它来作为数据库主键,它会存在以下几点问题。排序首先,生成的 ID 做好具有单调递增性,也就是有序的,而 UUID 不具备这个特点。为什么 ID 要是有序的呢?因为在系统设计时,ID 有可能成为排序的字段。我给你举个例子。比如,你要实现一套评论的系统时,你一般会设计两个表,一张评论表,存储评论的详细信息,其中有 ID 字段,有评论的内容,还有评论人 ID,被评论内容的 ID 等等,以 ID 字段作为分区键;另一个是评论列表,存储着内容 ID 和评论 ID 的对应关系,以内容 ID 为分区键。我们在获取内容的评论列表时,需要按照时间序倒序排列,因为 ID 是时间上有序的,所以我们就可以按照评论 ID 的倒序排列。而如果评论 ID 不是在时间上有序的话,我们就需要在评论列表中再存储一个多余的创建时间的列用作排序,假设内容 ID、评论 ID 和时间都是使用 8 字节存储,我们就要多出 50% 的存储空间存储时间字段,造成了存储空间上的浪费。有序ID会提升数据的写入性能我们知道 MySQL InnoDB 存储引擎使用 B+ 树存储索引数据,而主键也是一种索引。索引数据在 B+ 树中是有序排列的,就像下面这张图一样,图中 2,10,26 都是记录的 ID,也是索引数据。这时,当插入的下一条记录的 ID 是递增的时候,比如插入 30 时,数据库只需要把它追加到后面就好了。但是如果插入的数据是无序的,比如 ID 是 13,那么数据库就要查找 13 应该插入的位置,再挪动 13 后面的数据,这就造成了多余的数据移动的开销。我们知道机械磁盘在完成随机的写时,需要先做 “寻道” 找到要写入的位置,也就是让磁头找到对应的磁道,这个过程是非常耗时的。而顺序写就不需要寻道,会大大提升索引的写入性能。UUID不具备业务含义其实现实世界中使用的 ID 中都包含有一些有意义的数据,这些数据会出现在 ID 的固定的位置上。比如说我们使用的身份证的前六位是地区编号;7~14 位是身份证持有人的生日;不同城市电话号码的区号是不同的;你从手机号码的的前三位就可以看出这个手机号隶属于哪一个运营商。而如果生成的 ID 可以被反解,那么从反解出来的信息中我们可以对 ID 来做验证,我们可以从中知道这个 ID 的生成时间,从哪个机房的发号器中生成的,为哪个业务服务的,对于问题的排查有一定的帮助。最后,UUID 是由 32 个 16 进制数字组成的字符串,如果作为数据库主键使用比较耗费空间。你能看到,UUID 方案有很大的局限性,也是我不建议你用它的原因,而 twitter 提出的 Snowflake 算法完全可以弥补 UUID 存在的不足,因为它不仅算法简单易实现,也满足 ID 所需要的全局唯一性,单调递增性,还包含一定的业务上的意义。Snowflake 的核心思想是将 64bit 的二进制数字分成若干部分,每一部分都存储有特定含义的数据,比如说时间戳、机器 ID、序列号等等,最终生成全局唯一的有序 ID。它的标准算法是这样的:从上面这张图中我们可以看到,41 位的时间戳大概可以支撑 pow(2,41)/1000/60/60/24/365 年,约等于 69 年,对于一个系统是足够了。如果你的系统部署在多个机房,那么 10 位的机器 ID 可以继续划分为 2~3 位的 IDC 标示(可以支撑 4 个或者 8 个 IDC 机房)和 7~8 位的机器 ID(支持 128-256 台机器);12 位的序列号代表着每个节点每毫秒最多可以生成 4096 的 ID。不同公司也会依据自身业务的特点对 Snowflake 算法做一些改造,比如说减少序列号的位数增加机器 ID 的位数以支持单 IDC 更多的机器,也可以在其中加入业务 ID 字段来区分不同的业务。比方说我现在使用的发号器的组成规则就是:1 位兼容位恒为 0 + 41 位时间信息 + 6 位 IDC 信息(支持 64 个 IDC)+ 6 位业务信息(支持 64 个业务)+ 10 位自增信息(每毫秒支持 1024 个号)我选择这个组成规则,主要是因为我在单机房只部署一个发号器的节点,并且使用 KeepAlive 保证可用性。业务信息指的是项目中哪个业务模块使用,比如用户模块生成的 ID,内容模块生成的 ID,把它加入进来,一是希望不同业务发出来的 ID 可以不同,二是因为在出现问题时可以反解 ID,知道是哪一个业务发出来的 ID。实现方式那么了解了 Snowflake 算法的原理之后,我们如何把它工程化,来为业务生成全局唯一的 ID 呢?嵌入到业务代码里,也就是分布在业务服务器中这种方案的好处是业务代码在使用的时候不需要跨网络调用,性能上会好一些,但是就需要更多的机器 ID 位数来支持更多的业务服务器。另外,由于业务服务器的数量很多,我们很难保证机器 ID 的唯一性,所以就需要引入 ZooKeeper 等分布式一致性组件来保证每次机器重启时都能获得唯一的机器 ID。作为独立的发号器服务部署业务在使用发号器的时候就需要多一次的网络调用,但是内网的调用对于性能的损耗有限,却可以减少机器 ID 的位数,如果发号器以主备方式部署,同时运行的只有一个发号器,那么机器 ID 可以省略,这样可以留更多的位数给最后的自增信息位。即使需要机器 ID,因为发号器部署实例数有限,那么就可以把机器 ID 写在发号器的配置文件里,这样即可以保证机器 ID 唯一性,也无需引入第三方组件了。微博和美图都是使用独立服务的方式来部署发号器的,性能上单实例单 CPU 可以达到两万每秒。Snowflake 算法设计的非常简单且巧妙,性能上也足够高效,同时也能够生成具有全局唯一性、单调递增性和有业务含义的 ID,但是它也有一些缺点,其中最大的缺点就是它依赖于系统的时间戳,一旦系统时间不准,就有可能生成重复的 ID。所以如果我们发现系统时钟不准,就可以让发号器暂时拒绝发号,直到时钟准确为止。另外,如果请求发号器的 QPS 不高,比如说发号器每毫秒只发一个 ID,就会造成生成 ID 的末位永远是 1,那么在分库分表时如果使用 ID 作为分区键就会造成库表分配的不均匀。这一点,也是我在实际项目中踩过的坑,而解决办法主要有两个:时间戳不记录毫秒而是记录秒,这样在一个时间区间里可以多发出几个号,避免出现分库分表时数据分配不均。生成的序列号的起始号可以做一下随机,这一秒是 21,下一秒是 30,这样就会尽量的均衡了。我在开头提到,自己的实际项目中采用的是变种的 Snowflake 算法,也就是说对 Snowflake 算法进行了一定的改造,从上面的内容中你可以看出,这些改造:一是要让算法中的 ID 生成规则符合自己业务的特点;二是为了解决诸如时间回拨等问题。其实,大厂除了采取 Snowflake 算法之外,还会选用一些其他的方案,比如滴滴和美团都有提出基于数据库生成 ID 的方案。这些方法根植于公司的业务,同样能解决分布式环境下 ID 全局唯一性的问题。对你而言,可以多角度了解不同的方法,这样能够寻找到更适合自己业务目前场景的解决方案,不过我想说的是,方案不在多,而在精,方案没有最好,只有最适合,真正弄懂方法背后的原理,并将它落地,才是你最佳的选择。总结Snowflake 的算法并不复杂,你在使用的时候可以不考虑独立部署的问题,先想清楚按照自身的业务场景,需要如何设计 Snowflake 算法中的每一部分占的二进制位数。比如你的业务会部署几个 IDC,应用服务器要部署多少台机器,每秒钟发号个数的要求是多少等等,然后在业务代码中实现一个简单的版本先使用,等到应用服务器数量达到一定规模,再考虑独立部署的问题就可以了。这样可以避免多维护一套发号器服务,减少了运维上的复杂度。本文来自:极客时间 发号器:如何保证分库分表后ID的全局唯一性?
cAdvisor + Prometheus收集本机和docker容器数据在这个万物结可容器化的时代,监控显的尤为重要,在本篇文章,我们将对服务器的相关容器和本机数据利用 Cadvisor 进行收集,通过 Prometheus 作为数据源,利用 Grafana 进行展示。docker 或 kubernetes 集群的监控有多种,比如:docker: cAdvisor 收集本机以及容器的监控数据kubernetes:cAdvisor+InfluxDB+Grafanakubernetes:Heapster+InfluxDB+Grafana下面简单说一下谷歌 cAdvisor 和 普罗米修斯 结合的监控,在 grafana 展示,各个文档请看官方介绍https://prometheus.io/ https://github.com/google/cadvisor部署Cadvisor具体操作是在每台运行 docker 服务的主机上都跑一个 cAdvisor 容器, 博主这边是采用 docker-compose 的方式进行部署,然后我先给大家贴一下 ymlcadvisor: image: google/cadvisor restart: always ports: - "8080:8080" volumes: - /:/rootfs:ro - /var/run:/var/run:rw - /sys:/sys:ro - /var/lib/docker/:/var/lib/docker:ro当容器的 STATUS 为 UP 的时候就成功启动了, cAdvisor 为我们提供主机的信息的相关 API:http://IP:8080/metrics http://IP:8080/containers http://IP:8080/docker默认访问的的话,大概就是这个样子,也有相关数据的可视化图表,但是这远远不够,我们还是统一采用 grafana,进行集中式展示,方便查看各个主机的信息,在使用 grafana 之前,我们还需要将这些数据用 Prometheus 进行收集注意有可能启动会出现这个问题:W1112 08:18:11.930612 1 manager.go:349] Could not configure a source for OOM detection, disabling OOM events: open /dev/kmsg: no such file or directory E1112 08:18:58.568289 1 static.go:75] Failed to write response: write tcp 172.19.0.6:8080->61.140.208.205:53633: write: broken pipe W1112 08:48:55.102473 1 container.go:409] Failed to create summary reader for "/docker/42ef2fbef15ce20a5fc2caaebaccc7712b179855ce8384f34cbdae1f7d30920f": none of the resources are being tracked. I1114 02:36:19.568227 1 manager.go:1212] Exiting thread watching subcontainers I1114 02:36:19.568254 1 manager.go:432] Exiting global housekeeping thread I1114 02:36:19.568269 1 cadvisor.go:212] Exiting given signal: terminated W1114 02:36:20.574582 1 manager.go:349] Could not configure a source for OOM detection, disabling OOM events: open /dev/kmsg: no such file or directory I1114 02:37:41.416194 1 manager.go:1212] Exiting thread watching subcontainers I1114 02:37:41.416215 1 manager.go:432] Exiting global housekeeping thread I1114 02:37:41.416227 1 cadvisor.go:212] Exiting given signal: terminated W1114 02:37:42.407178 1 manager.go:349] Could not configure a source for OOM detection, disabling OOM events: open /dev/kmsg: no such file or directory之后处理方法如下:sudo mount -o remount,rw '/sys/fs/cgroup' sudo ln -s /sys/fs/cgroup/cpu,cpuacct /sys/fs/cgroup/cpuacct,cpu部署Prometheus这个我们也采用 docker-compose 进行部署,统一方便prometheus: image: prom/prometheus:latest restart: always ports: - "9090:9090" volumes: - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml - type: volume source: prometheus target: /prometheus volumes: prometheus: driver: local driver_opts: type: none o: bind device: /var/www/ghost-docker-compose/prometheus/dataprometheus.ymlglobal: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: 'prometheus' static_configs: - targets: ['prometheus:9090'] - job_name: 'cadvisor' static_configs: - targets: ['cadvisor:8080']成功后方问 ip:9090 , 就能看到界面了,然后我们配置一下 Prometheus 让他能看到 Cadvisor 这个服务,Status -> Targets 看到 UP 状态就是成功了在 Status-Configuration 里面可以看到配置文件,Targets 里面查看已经建立的节点。普罗米修斯 Graph 搜索查看条件语法见 https://prometheus.io/docs/querying/basics/我们可以在 Graph 栏目选择一条指令测试一下,是否可以成功获取到信息部署Grafanagrafana 是从 prometheus 获取数据并展示,第一步添加 prometheus 数据库,第二步用网上的模板画图,这边直接 docker 一条龙grafana: image: grafana/grafana:latest volumes: - type: volume source: grafana target: /var/lib/grafana ports: - "3000:3000" volumes: prometheus: driver: local driver_opts: type: none o: bind device: /var/www/ghost-docker-compose/prometheus/data grafana: driver: local driver_opts: type: none o: bind device: /var/www/ghost-docker-compose/grafana添加DataSource我们先进行 data Source 的添加关于 Access 的选择访问模式控制如何处理对数据源的请求。如果没有其他说明,则 Server 应该是首选方式。服务器访问模式(默认):所有请求都将从浏览器发出到Grafana后端/服务器,后者再将请求转发到数据源,从而避免可能的跨域资源共享(CORS)要求。 如果选择此访问方式,则需要可以从grafana后端/服务器访问该URL。浏览器访问模式:所有请求都将从浏览器直接向数据源发出,并且可能会受到跨域资源共享(CORS)的要求。 如果选择此访问方式,则需要可以从浏览器访问URL。导入图表-可视化查看然后我们需要 import 一个图表对数据进行可视化,但是图表从哪里来呢?官方已经有很多图表,所以我们需要找到合适自己的图表,并记录下他们的图表 ID 这样我们就可以远程加载了。这边给大家找了一个 ID: 11600选择我们上个步骤添加的数据源,这样就可以看到 Cadvisor 收集的信息啦
Linux Shell命令速查表终端命令日志统计独立 IP 数量awk '{print $1}' access.log | sort -n | uniq | wc -l查看某一时间段的 IP 访问量grep "05/Apr/2019:0[1-9]" access.log | awk '{print $1}' | sort | uniq -c| sort -nr | wc -l查看访问最频繁的前 100 个 IPawk '{print $1}' access.log | sort -n | uniq -c | sort -rn | head -n 100查看访问 100 次以上的 IPawk '{print $1}' access.log | sort -n | uniq -c | awk '{if($1 > 100) print $0}' | sort -rn查询某个 IP 的详细访问情况,按访问频率排序grep '127.0.0.1' access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -n 100统计 URL 访问量排行awk '{url[$7]++} END {for (k in url) {print url[k],k}}' nginx.access.log | sort -rn使用 awk 从 Nginx 日志中逐行统计 URL 访问计数,然后使用 sort 对结果进行排名访问最频繁的 URLawk '{print $7}' access.log | sort | uniq -c | sort -rn | head -n 100或者awk '{url[$7]++} END {for (k in url) {print url[k],k}}' access.log | sort -rn | head -n 100除了 .php 以外,访问最频繁的 URLgrep -v ".php" access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -n 100URL 访问次数超过 100 次的页面awk '{print $7}' access.log | sort -n | uniq -c | sort -rn | head -n 100查看最近1000条记录,访问量最高的 URLtail -1000 access.log | awk '{print $7}' | sort | uniq -c | sort -rn | less统计每秒的请求数,TOP100的时间点(精确到秒)awk '{print $4}' access.log | cut -c 14-21 | sort | uniq -c | sort -rn | head -n 100统计每小时的请求数,TOP100的时间点(精确到小时)awk '{print $4}' access.log | cut -c 14-15 | sort | uniq -c | sort -rn | head -n 100列出传输时间超过3秒的页面,并统计其出现的次数,显示前20条在 Nginx log 最后一个字段加入 $request_timecat access.log | awk '($NF > 3){print $7}' | sort -n | uniq -c | sort -rn | head -20列出PHP页面请求时间超过3秒的页面,并统计其出现的次数,显示前100条在 Nginx log 最后一个字段加入 $request_timecat access.log | awk '($NF > 1 && $7~/\.php/){print $7}' | sort -n | uniq -c | sort -rn | head -100文件列出当前目录下的所有文件(包括隐藏文件)的绝对路径find $PWD -maxdepth 1 | xargs ls -ld递归列出当前目录下的所有文件(包括隐藏文件)的绝对路径find $PWD | xargs ls -ld在每行记录的开头加上当前路径ls | sed "s:^:`pwd`/:"删除指定时间之前的文件find /path/to/dir -mtime +30 -type f | xargs rm -f/path/to/dir 设置查找的目录--mtime +30 设置时间为30天前-type f 指定查找的类型为文件删除文件前/后N行删除了前2行。先用 tail 把从第3行开始的所有内容输出到新文件,然后再重命名文件。tail -n +3 old_file > new_file mv new_file old_file仅保留最后3行。tail -n -3 old_file > new_file mv new_file old_file如果写定时任务,那可放置到一行。0 0 * * * tail -n -3 old_file > new_file && mv -f new_file old_file网络统计网卡的流量数据sar -n DEV 1 5 平均时间: IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s 平均时间: lo 2.21 2.21 0.18 0.18 0.00 0.00 0.00 平均时间: eth0 4.62 3.82 0.37 1.90 0.00 0.00 0.00命令中 1 5 表示每一秒钟取 1 次值,一共取 5 次。命令执行后会列出每个网卡这 5 次取值的平均数据,根据实际情况来确定带宽跑满的网卡名称,默认情况下 eth0 为内网网卡,eth1 为外网网卡。查询占用端口的进程/程序netstat -tunlp | grep ':80' tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 26655/nginx或者使用 lsof 命令:lsof -i :80查看流量占用情况iftop -P查看程序流量排行nethogs进程/程序grep 程序并杀死ps -ef | grep process_name | grep -v grep | cut -c 9-15 | xargs kill -s 9查看指定进程的具体占用内存cat /proc/[pid]/status Name: memcached State: S (sleeping) Tgid: 1954 Pid: 1954 PPid: 1 TracerPid: 0 Uid: 500 500 500 500 Gid: 500 500 500 500 Utrace: 0 FDSize: 128 Groups: VmPeak: 413792 kB VmSize: 360544 kB VmLck: 0 kB VmHWM: 29704 kB VmRSS: 29376 kB VmData: 341768 kB VmStk: 2132 kB VmExe: 80 kB VmLib: 2152 kB VmPTE: 164 kB VmSwap: 0 kB Threads: 6 ...其中,VmRSS 项表示实际占用内存值。或者,用 ps 命令ps aux | grep <pid>USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND jxcdn 1954 0.0 0.1 360544 29376 ? Ssl Apr13 7:56 memcached -m 128 -p 11211其中 RSS 列表示实际使用内存(单位: KB)。可以看出,与 /proc/[pid]/status 的值是一致的。脚本命令获取脚本文件所在目录script_path=$(cd `dirname $0`; pwd)获取脚本文件的上级目录script_path=$(cd `dirname $0`; pwd) root_path=$(cd `dirname "$script_path"`; pwd)格式化当前时间datetime=$(date +"%Y-%m-%d %H:%M:%S")去除文本中的颜色转义符sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g"来源: https://www.commandlinefu.com/commands/view/3584/remove-color-codes-special-characters-with-sed
kafka高可用集群搭建说明这篇博文主要是为了后面的 elk 做准备,我们这里搭建一个 kafka 集群,使用2个节点,还是前面的节点。主要是为了后面做数据缓冲。这里不对 mq 的详细进行介绍,必要会对 kafka 相关配置进行描述。节点说明节点hostname192.168.179.123node-5192.168.179.124node-4192.168.179.125node-3当我们进行集群搭建的时候,要注意节点数量应该为基数,要求 可用节点数量 > 总节点数量/2,我们采用最少节点数:3台安装配置kafkahttps://mirror.bit.edu.cn/apache/kafka/2.5.0/kafka_2.12-2.5.0.tgz tar -zxvf kafka_2.12-2.5.0.tgzvi config/server.properties# node-5 broker.id=1 listeners=PLAINTEXT://192.168.179.123:9092 offsets.topic.replication.factor=3 transaction.state.log.replication.factor=3 transaction.state.log.min.isr=3 zookeeper.connect=192.168.179.124:2181,192.168.179.123:2181,192.168.179.125:2181 # node-4 broker.id=0 listeners=PLAINTEXT://192.168.179.124:9092 offsets.topic.replication.factor=3 transaction.state.log.replication.factor=3 transaction.state.log.min.isr=3 zookeeper.connect=192.168.179.124:2181,192.168.179.123:2181,192.168.179.125:2181 # node-3 也一样 就broker.id = 2kafka 配置说明keyvaluedelete.topic.enable=true是否允许删除topic,默认false不能手动删除broker.id=0当前机器在集群中的唯一标识,和zookeeper的myid性质一样listeners = PLAINTEXT://192.168.100.151:9092当前kafka服务侦听的地址和端口,端口默认是9092num.network.threads=3这个是borker进行网络处理的线程数num.io.threads=8这个是borker进行I/O处理的线程数socket.send.buffer.bytes=102400发送缓冲区buffer大小,数据不是一下子就发送的,先会存储到缓冲区到达一定的大小后在发送,能提高性能socket.receive.buffer.bytes=102400kafka接收缓冲区大小,当数据到达一定大小后在序列化到磁盘socket.request.max.bytes=104857600这个参数是向kafka请求消息或者向kafka发送消息的请请求的最大数,这个值不能超过java的堆栈大小log.dirs=消息日志存放的路径num.partitions=1默认的分区数,一个topic默认1个分区数num.recovery.threads.per.data.dir=1每个数据目录用来日志恢复的线程数目log.retention.hours=168默认消息的最大持久化时间,168小时,7天log.segment.bytes=1073741824这个参数是:因为kafka的消息是以追加的形式落地到文件,当超过这个值的时候,kafka会新起一个文件log.retention.check.interval.ms=300000每隔300000毫秒去检查上面配置的log失效时间log.cleaner.enable=false是否启用log压缩,一般不用启用,启用的话可以提高性能zookeeper.connect=node1:2181,node2:2181,node3:2181设置zookeeper的连接端口broker.id=0当前机器在集群中的唯一标识,和zookeeper的myid性质一样zookeeper.connection.timeout.ms=6000设置zookeeper的连接超时时间vi config/zookeeper.properties为了保证 kafka 的高可用,我们需要配置 zookeeper三台主机配置保持一致即可dataDir=/tmp/zookeeper # 客户端连接server的端口,即对外服务端口,默认为2181 clientPort=2181 # 单个客户端与单台服务器之间的连接数的限制,是ip级别的,默认是60,如果设置为0,那么表明不作任何限制。请注意这个限制的使用范围,仅仅是单台客户端机器与单台ZK服务器之间的连接数限制,不是针对指定客户端IP,也不是ZK集群的连接数限制,也不是单台ZK对所有客户端的连接数限制 maxClientCnxns=0 admin.enableServer=false # server列表 2888为选举端口,3888为心跳端口 server.0=192.168.179.124:2888:3888 # 0表示服务器的编号 对应 dataDir 下面的 myid 文件 server.1=192.168.179.123:2888:3888 server.2=192.168.179.125:2888:3888 # ZK中的一个时间单元。ZK中所有时间都是以这个时间单元为基础,进行整数倍配置的。例如,session的最小超时时间是2*tickTime tickTime=2000 # Follower在启动过程中,会从Leader同步所有最新数据,然后确定自己能够对外服务的起始状态。Leader允许F在 initLimit时间内完成这个工作。通常情况下,我们不用太在意这个参数的设置。如果ZK集群的数据量确实很大了,F在启动的时候,从Leader上同步数据的时间也会相应变长,因此在这种情况下,有必要适当调大这个参数 initLimit=10 # 在运行过程中,Leader负责与ZK集群中所有机器进行通信,例如通过一些心跳检测机制,来检测机器的存活状态。如果L发出心跳包在syncLimit之后,还没有从F那里收到响应,那么就认为这个F已经不在线了。注意:不要把这个参数设置得过大,否则可能会掩盖一些问题 syncLimit=5在上一步 dataDir 指定的目录下,创建 myid 文件。 直接将 kafka 的 broker.id 写入对应即可节点hostnamemyid192.168.179.123node-5echo 2 > /tmp/zookeeper/myid192.168.179.124node-4echo 0 > /tmp/zookeeper/myid192.168.179.125node-3echo 1 > /tmp/zookeeper/myid启动集群所有节点都需要执行./bin/zookeeper-server-start.sh -daemon ./config/zookeeper.properties ./bin/kafka-server-start.sh -daemon ./config/server.properties进程正常启动表示成功,查看一下生产消息+消费消息测试./bin/kafka-console-producer.sh --bootstrap-server 192.168.179.124:9092 --topic test-messages ./bin/kafka-console-consumer.sh --bootstrap-server 192.168.179.124:9092 --topic test-messages --from-beginning现在 kafka 可用确认生产消费是正常的了容错测试集群可用性虽然说两个节点的 kafka 正常启动了,我们还需要对他的可用性进行测试,保证到时候我们服务的一个高可用。测试:我们创建一个 topic ,我们通过查看这个 topic 里面的 leader,如果说 leader 的 kafka 服务出现问题了,会不会切换到另一个节点,做这个测试的前提是配置好副本数,没有冗余的话是没有可用性的话可能有点绝对,这里我想表明的实际是冗余是解决可用性的方案创建 topic./bin/kafka-topics.sh --create --bootstrap-server 192.168.179.123:9092,192.168.179.124:9092,192.168.179.125:9092 --replication-factor 3 --partitions 1 --topic test-ha-kafka # --replication-facotor 1 两个副本 # --partitions 1 创建1个分区 # --topic 主题为test查看 topic 的 leader./bin/kafka-topics.sh --describe --bootstrap-server 192.168.179.123:9092,192.168.179.124:9092,192.168.179.125:9092 --topic test-ha-kafka我们手动制造错误,看集群的容错性,我们将 broker.id = 0 的 kafka 手动停止,看看 topic 的 leader 会不会自动切换现在已经完成了我们的高可用测试,但是我们对 kafka 的管理老是通过命令行处理非常麻烦,然后给大家介绍一下 kafka 的可视化工具: kafkatool、或者可以使用 kafka-manager可视化kafkatool使用下载地址:https://www.kafkatool.com/,根据自己的系统下载这样就方便多了,还可以看到我们前面测试的 topic ,搭建好这个集群先留着,要用来优化我们的 日志系统 哦。
ELK7.x日志系统搭建 1. elk基础搭建基本介绍什么是 ELK ? 通俗来讲, ELK 是由 Elasticsearch 、 Logstash 、 Kibana 三个开源软件组成的一个组合体,这三个软件当中,每个软件用于完成不同的功能,他们组成了一套完整的日志系统的解决方案。Logstash 对各个服务的日志进行采集、过滤、推送。Elasticsearch 存储 Logstash 传送的结构化数据,提供给 Kibana 。Kibana 提供用户 UIweb 页面进行,数据展示和分析形成图表等。环境介绍暂时是准备了两个节点,版本均为7.6,JDK是系统的 openJDK,系统为 ubuntu18.04机器安装程序192.168.179.123Es Logstash192.168.179.124Es Kibana LogstashElasticsearch集群配置下载地址:https://mirrors.huaweicloud.com/elasticsearch/7.6.0/elasticsearch-7.6.0-linux-x86_64.tar.gz修改配置elasticsearch.ymlcluster.name: elk-application #ELK的集群名称,名称相同即属于是同一个集群 node.name: node-5 #本机在集群内的节点名称 要在集群中唯一 path.data: /elk/data #数据存放目录 path.logs: /elk/logs #日志保存目录 network.host: 192.168.179.123 #监听的IP地址 http.port: 9200 #服务监听的端口 discovery.seed_hosts: ["192.168.179.124", "192.168.179.123"] # 提供集群中符合主机要求的节点的列表 服务发现种子主机 cluster.initial_master_nodes: ["192.168.179.124", "192.168.179.123"] # 可以成为master节点的机器 初始主节点 #开启 xpack 功能,如果要禁止使用密码,请将以下内容注释,直接启动不需要设置密码 xpack.security.enabled: true xpack.security.transport.ssl.enabled: true xpack.security.transport.ssl.verification_mode: certificate xpack.security.transport.ssl.keystore.path: elastic-certificates.p12 xpack.security.transport.ssl.truststore.path: elastic-certificates.p12各节点修改 config/jvm.options 文件的 -Xms4g 和 -Xmx4g 为服务器的内存一半,我的服务器时 4G 内存,所以这里改成了 2G 。当然,这个值最大不要超过 32G 。因为没有意义,具体查阅文档创建es账号useradd elastic passwd elastic mkdir -p /elk/{data,logs} chown elasticsearch.elasticsearch /elk/ -R chown elastic elasticsearch-7.6.0 -R生成TLS 和身份验证bin/elasticsearch-certutil cert -out config/elastic-certificates.p12 -pass ""生成 TLS 和身份验证,将会在config下生成elastic-certificates.p12文件,将此文件传到其他节点的config目录,注意文件权限。启动集群,es 集群不启动,下面的添加密码操作执行不了。每个节点执行(我是在root用户下指定适用 elastic 用户启动的,如果是在elastic 用户下执行,请直接启动)su - elastic -c "/elasticsearch-7.6.0/bin/elasticsearch -d"创建 Elasticsearch 集群密码,有两种方式,使用第二种。在主节点上执行,我所有的密码都设置成了 123456第一种: bin/elasticsearch-setup-passwords auto 各用户生成随机密码。第二种: bin/elasticsearch-setup-passwords interactive 手动定义密码,可以设置如下图所示的用户密码。bin/elasticsearch-setup-passwords interactive设置密码后通过浏览器去流量就需要输入账号密码了,但是在修改集群密码的过程中有可能失败,错误如下:Unexpected response code [503] from calling PUT http://192.168.179.124:19200/_security/user/apm_system/_password?pretty Cause: Cluster state has not been recovered yet, cannot write to the [null] index Possible next steps: * Try running this tool again. * Try running with the --verbose parameter for additional messages. * Check the elasticsearch logs for additional error details. * Use the change password API manually. ERROR: Failed to set password for user [apm_system].解决:集群中的两台服务器需要同时设置,并全部重新启动Kibana 配置下载地址:https://mirrors.huaweicloud.com/kibana/7.6.0/kibana-7.6.0-linux-x86_64.tar.gzvim config/kibana.ymlserver.port: 5601 server.host: "192.168.179.124" elasticsearch.hosts: ["192.168.179.124:9200"] elasticsearch.username: "elastic" elasticsearch.password: "123456"启动sudo nohup ./bin/kibana --allow-root使用我们的账号密码进行登录在控制台查看集群状态Logstash 配置下载地址:https://mirrors.huaweicloud.com/logstash/7.6.0/logstash-7.6.0.tar.gz配置kibana监控logstash我们的 es 是开启了密码验证的,要想将数据推到 es 里面,需要进行配置,密码尽量不使用明文./bin/logstash-keystore create上面的命令将创建一个 Created Logstash keystore我们可以利用如下的命令来创建一些key: ES_HOST 及 ES_PWD 。./bin/logstash-keystore add ES_HOST # http://192.168.179.124:9200/ ./bin/logstash-keystore add ES_PWD # 123456logstash.ymlxpack.monitoring.enabled: true xpack.monitoring.elasticsearch.username: logstash_system xpack.monitoring.elasticsearch.password: "${ES_PWD}" xpack.monitoring.elasticsearch.hosts: ["${ES_HOST}"]mkdir conf.d vi system-log.conf ###### 配置文件 ###### input { file { path => "/var/log/syslog.1" #日志路径 type => "systemlog" #类型,自定义,在进行多个日志收集存储时可以通过该项进行判断输出 start_position => "beginning" #logstash 从什么位置开始读取文件数据,默认是结束位置,也就是说 logstash 进程会以类似 tail -F 的形式运行。如果你是要导入原有 数据,把这个设定改成 "beginning",logstash 进程就从头开始读取,类似 less +F 的形式运行。 stat_interval => "2" #logstash 每隔多久检查一次被监听文件状态(是否有更新),默认是 1 秒 } } output { elasticsearch { hosts => ["192.168.179.124:9200"] #elasticsearch服务器地址 user => "elastic" password => "${ES_PWD}" index => "logstash-%{type}-%{+YYYY.MM.dd}" #索引名称 } }同时,我们需要添加 hosts , user 及 password 的定义。这是因为我们现在我们是需要有用户名及密码才可以连接到 Elasticsearch 。同时我们可以创建自己的用户名及密码。我们可以参考“Elasticsearch:用户安全设置”来创建自己喜欢的账号。在这里,为了方便,我们使用elastic账号。在这里,我们是用${ES_HOST}及${ES_PWD}来代表我们的Elasticsearch地址及密码。这样的好处是我们不暴露我们的密码在配置文件中。./bin/logstash -f conf.d/system-log.conf启动后会每两秒去收集系统日志到 es ,然后监控里面也能看见 logstash 了在kibana中查看收集到的数据默认的话,我们只能看见 es 的集群和 kibana 的信息,我们为了方便可以把 logstash 也添加进来测试的话:echo test-logstash >> /var/log/syslog.1,然后间隔一会就能看见命中次数改变了现在我们完成了基本的数据收集,我们的预期是做一个高效率的日志系统,收集我们应用中的各种日志,方便我们查看和分析,例如:mysql、redis、nginx、框架日志、集群日志、系统日志等等,我们现在完成了第一步,算是搭建了一个基础的日志系统,在接下来我们慢慢完善这个系统。现在系统主要是利用 logstash 进行日志收集,但是 logstash 消耗较大,每台安装进行收集的话成本会比较高,我们后面可以引用一些 队列 或其他 收集程序 进行优化。参考资料https://blog.csdn.net/UbuntuTouch/article/details/103767088https://blog.csdn.net/qq_31547771/article/details/100665922
到了k8s的文章了, 博主前面介绍了swarm集群, swarm集群本身相对来说比较简单、 轻量, 所以并没有重点介绍, 但是k8s不太一样, 这玩意还是比较复杂, 一两篇简单介绍不完, 所以博主这边得细说几篇, 最后也会做个实例, 方便大家参考。选择swarm还是k8s,两者有什么区别?背景不一样, k8s是谷歌, swarm是官网方案swarm轻量, 直接docker就有了, k8s还得安装且较为复杂。这里就出现一个点, 简单意味着部署运维成本低, 所以成本这方面, k8s要高不少k8s集群完善, 最小单元pod比swarm的service更加强大还一个很重要的就是k8s健康机制完善, Replication Controllers可以监控并维持容器的生命在网络上k8s选用Flannel作为overlay网络,可以让每一个使用 Kuberentes 的CoreOS 主机拥有一个完整的子网。组件这方面还是k8s占据优势, 启动速度swarm要快, 它只需要两层交换, 而k8s需要五层内置负载均衡k8s要🐂,比较完善弹性伸缩: 弹性伸缩是指根据宿主机硬件资源承载的情况而做出的一种容器部署架构动态变化的过程。比如某台宿主机的CPU使用率使用偏高,k8s可以根据Pod的使用率自动调整一个部署里面Pod的个数,保障服务可用性,但swarm则不具备这种能力。两者架构图对比swarmk8s网上找的两张图, 大家可以看看, 明显k8s看着要线段要多, 等大家玩了这两个集群再来看看这个架构图比较好, 现在只是让大家看看里面的基本概念k8s的重要知识点 (要想明白得懂啊,不懂也先看看)一般高可用集群副本数据最好是 > 3 的奇数 (5,7,9...),主要是方便选举leader非常重要,要考的, k8s最小单元podpod由一个或者多个为实现某个特定功能而放在一起的容器组成。在pod内的docker共享volume和网络namespace,彼此之间可以通过localhost通信或者标准进程间通信。用pod有什么好处呢?我们试想这样一个场景:我们有一个web应用的容器,现在我们为了收集web日志需要安装一个日志插件,如果把插件安装在web应用容器的里面,则会面临如下一些问题:如果插件有更新,尽管web应用没有变化,但因为两者共享一个镜像,则必须把整个镜像构建一遍;如果插件存在内存泄露的问题,整个容器就会有被拖垮的风险如果把插件安装在不同的容器,同样也不合适,因为你要想办法解决插件所在容器读取web容器的日志的问题。有了pod以后,这些问题都可以迎刃而解。在pod里面为日志插件和web应用各自创建一个容器,两者共享volume,web应用容器只需将日志保存到volume,变可以很方便的让日志插件读取。同时,两个容器拥有各自的镜像,彼此更新互不影响。Pod 网络 flannelpod之间能够通信网络, 需要部署网络, 其中就可以选择flannel来解决复杂产品系统拆分之namepsace在复杂系统中,团队会非常容易意外地或者无意识地重写或者中断其他服务service。相反,请创建多个命名空间来把你的服务service分割成更容易管理的块。命名空间具有一定隔离性, 但是并不是绝对的隔离,一个namespace中service可以和另一个namespace中的service通信。apiserver这玩意, 就是对外提供api的, 可以让更多客户端灵活控制集群, 例如我们后面使用的kubectl, 通过调用apiserver的api很方便对集群进行操作, 谁用谁知道, 真好!etcd这可是个厉害的东西, 是一个单独项目, 在k8s是个很重要的组件etcd是CoreOS团队于2013年6月发起的开源项目,它的目标是构建一个高可用的分布式键值(key-value)数据库。etcd内部采用raft协议作为一致性算法,etcd基于Go语言实现。整个kubernetes系统中一共有两个服务需要用到etcd用来协同和存储配置:网络插件flannel、对于其它网络插件也需要用到etcd存储网络的配置信息kubernetes本身,包括各种对象的状态和元信息配置,当数据发生变化会快速通知kubernetes相关组件kubelet 代理人在kubernetes集群中,每个Node节点都会启动kubelet进程,用来处理Master节点下发到本节点的任务,管理Pod和其中的容器。kubelet会在API Server上注册节点信息,定期向Master汇报节点资源使用情况,并通过cAdvisor监控容器和节点资源。可以把kubelet理解成【Server-Agent】架构中的agent,是Node上的pod管家。直接和容器引擎交互实现容器的生命周期管理controller-manager运行控制器,它们是处理集群中常规任务的后台线程。逻辑上,每个控制器是一个单独的进程,但为了降低复杂性,它们都被编译成独立的可执行文件,并在单个进程中运行。这些控制器包括:节点控制器(Node Controller): 当节点移除时,负责注意和响应。副本控制器(Replication Controller): 负责维护系统中每个副本控制器对象正确数量的 Pod。端点控制器(Endpoints Controller): 填充 端点(Endpoints) 对象(即连接 Services & Pods)。服务帐户和令牌控制器(Service Account & Token Controllers): 为新的namespace创建默认帐户和 API 访问令牌.schedulerScheduler负责Pod调度。在整个系统中起"承上启下"作用,承上:负责接收Controller Manager创建的新的Pod,为其选择一个合适的Node;启下:Node上的kubelet接管Pod的生命周期。通过调度算法为待调度Pod列表的每个Pod从Node列表中选择一个最适合的Node,并将信息写入etcd中kubelet通过API Server监听到kubernetes Scheduler产生的Pod绑定信息,然后获取对应的Pod清单,下载Image,并启动容器。proxy这也挺🐂, 是做转发的, 还能实现内部负载均衡,例如说:一般service在逻辑上代表了后端的多个Pod,外界通过service访问Pod。service接收到的请求就是通过kube-proxy转发到Pod上的。每个Node都会运行kube-proxy服务,它负责将访问service的TCP/UDP数据流转发到后端的容器。如果有多个副本,kube-proxy会实现负载均衡。coredns可以为集群中的svc创建一个域名IP的对应关系解析, pod之间可以直接利用coredns生成域名来进行访问dashboard给集群提供B/S结构访问体系ingress controller官方只能实现四层代理, 这玩意实现七层 🐂🍺federation跨集群中心多k8s统一管理prometheus集群监控elk集群日志统一分析介入平台差不多了, 再啥有再更新吧, 这些理论总结于互联网, 我实在没法全面写出这些东西, 我白话一些没问题, 也当做个笔记, 看官们也可以整理一下这些概念, 看完这些再去看看架构图, 应该会清晰不少结束语博主之所以学习这个集群, 那完全是公司现在集群方面全转k8s了, 以前还是使用swarm的, 所以也没必要为了追求高端一点的技术选择k8s(但是现在阿里云已经将swarm下架了,没法在阿里上玩了), 不要增加了软件开发难度, 适合自己的最好。(不过使用阿里生态没得选了,阿伟)综上所述???! 偏向于k8s, 果然, 越南的越吃香。下篇更新一下k8s的相关操作!!!!记得来看。
在使用docker中, 我们经常会使用到一些镜像, 但是往往我们使用的都是基础镜像。想要使用到一些工具, 只能再进入容器安装, 那能不能我们自定义镜像, 比方说 我默认拉一个镜像, 这个镜像默认就安装了nginx, 或者默认已经安装了vim?因为镜像的里面有什么东西是利用Dockerfile来声明的,也就是我们想要实现自定义功能我必须得懂Dockerfile。Dockerfile实际上就是一个文本, 然后编写docker可以认识的语法, 那么就可以构建我们想要的镜像。Dockerfile构建Nginx镜像FROM centos:centos7 # 从centos基础镜像构建 # 设置工作目录 WORKDIR "/tmp" # 这里远程下载太慢了 我直接本地弄得。大家可以使用wget来进行远程下载 ADD nginx-1.17.5.tar.gz /tmp # 添加nginx用户 RUN useradd -M -s /sbin/nologin nginx # 安装相关依赖 RUN yum -y install gcc* make pcre-devel zlib-devel openssl openssl-devel libxslt-devel gd gd-devel GeoIP GeoIP-devel pcre pcre-devel \ && cd nginx-1.17.5 \ && ./configure --user=nginx --group=nginx --prefix=/usr/local/nginx --with-file-aio --with-http_ssl_module --with-http_realip_module --with-http_addition_module --with-http_xslt_module --with-http_image_filter_module --with-http_geoip_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_auth_request_module --with-http_random_index_module --with-http_secure_link_module --with-http_degradation_module --with-http_stub_status_module && make && make install \ && echo "hello aoppp.com" > /usr/local/nginx/html/index.html # 表示对外期望暴露得端口 EXPOSE 80 # 启动nginx 将nginx主进程 pid为1 nginx一旦挂掉那么docker容器就会直接退出 CMD ["/usr/local/nginx/sbin/nginx", "-g", "daemon off;"]然后我们将dockerfile构建出来, 运行以下命令来使用nginx镜像docker build -f nginxDockerfile -t aoppp/nginx:v1 . # 指定dockerfile构建 提供上下文环境 docker run --name aoppp-nginx -d -p 80:80 aoppp/nginx:v1 # 后台启动一个容器 并进行端口映射看看localhost 已经成功了上传到docker hub我们拉取的镜像默认都是从docker hub来的, 如果我们做的镜像想要被别人直接pull下来, 我们也需要上传到docker hub。docker hub地址没有账号的要先创建账号呀,博主这边已经准备好了呀。使用docker login登录账号, 在docker hub上面创建我们需要上传的仓库给创建好的镜像打上标签# 这里 aoppp/nginx:v1本地镜像的名字 testsmile/aoppp:v1是重新启的名字 是和远程的要对上。 docker tag aoppp/nginx:v1 testsmile/aoppp:v1 docker push testsmile/aoppp:v1 # 推送至远程仓库推送成功, 并且搜索一下呀!从远程拉下来本地构建一下试试docker pull testsmile/aoppp:v1 docker run --name aoppp-nginx -d -p 80:80 testsmile/aoppp:v1访问localhost这样就已经ok了编写dockerfile需要注意将多个RUN指令合并为一个, 因为Docker镜像是分层的Dockerfile中的每个指令都会创建一个新的镜像层。镜像层将被缓存和复用当Dockerfile的指令修改了,复制的文件变化了,或者构建镜像时指定的变量不同了,对应的镜像层缓存就会失效某一层的镜像缓存失效之后,它之后的镜像层缓存都会失效镜像层是不可变的,如果我们再某一层中添加一个文件,然后在下一层中删除它,则镜像中依然会包含该文件(只是这个文件在Docker容器中不可见了)。Docker镜像类似于洋葱。它们都有很多层。为了修改内层,则需要将外面的层都删掉。记住这一点的话,其他内容就很好理解了。因此,我建议大家为每个应用构建单独的Docker镜像,然后使用 Docker Compose 运行多个Docker容器。容器只运行单个应用从技术角度讲,你可以在Docker容器中运行多个进程。你可以将数据库,前端,后端,ssh,supervisor都运行在同一个Docker容器中。但是,这会让你非常痛苦:非常长的构建时间(修改前端之后,整个后端也需要重新构建)非常大的镜像大小多个应用的日志难以处理(不能直接使用stdout,否则多个应用的日志会混合到一起)横向扩展时非常浪费资源(不同的应用需要运行的容器数并不相同)僵尸进程问题 - 你需要选择合适的init进程COPY与ADD优先使用前者 合理调整COPY与RUN的顺序我们应该把变化最少的部分放在Dockerfile的前面,这样可以充分利用镜像缓存。选择合适的基础镜像(alpine版本最好)alpine是最精简的linux编写.dockerignore文件构建镜像时,Docker需要先准备context ,将所有需要的文件收集到进程中。默认的context包含Dockerfile目录中的所有文件,但是实际上,我们并不需要.git目录,node_modules目录等内容。 .dockerignore 的作用和语法类似于 .gitignore,可以忽略一些不需要的文件,这样可以有效加快镜像构建时间,同时减少Docker镜像的大小。示例如下:.git/ node_modules/快去看看其他文章!!!!!!! 😠😠
2023年07月