依据 GitLab Flow 模型修改简化。若想深入了解相关模型,可点击链接阅读详情。
版本格式:主版本号.次版本号.修订号,版本号递增规则如下:
主版本号:当你做了不兼容的 API 修改,
次版本号:当你做了向下兼容的功能性新增,
修订号:当你做了向下兼容的问题修正。先行版本号及版本编译元数据可以加到“主版本号.次版本号.修订号”的后面,作为延伸。
主版本号为 0,代表还未发布正式版本。
每次提交 commit 之前一定要编写具有完整语义化的 commit message,否则会拒绝合并。
1 | git commit -m "fix:fix #1" |
每次提交的 message 都包括三个部分 Header,Body 和 Footer,格式如下
1 | <type>(<scope>):<subject> |
其中 Header 是必需的,Body 和 Footer 可省略。
无论是哪一个部分,任何一行都不能超过 72 个字符(或者100个字符)避免自动换行。
Header 部分只有一行,包括三个字段:type(必须)、scope(可选)、subject(必须)。
(1) type
用于说明 commit 的类别,只允许使用下面的关键字
关键字 | 说明 |
---|---|
init | 项目初始化 |
feat | 添加新特性 |
fix | 修复 bug |
docs | 文档改进,包括代码注释 |
style | 仅仅修改了空格、格式缩进、逗号等等,不改变代码逻辑(对结构有影响的用 refactor) |
refactor | 代码重构,没有加新功能或者修复 bug |
perf | 优化相关,比如提升性能、体验 |
test | 增加测试用例 |
build | 依赖相关的内容 |
ci | ci 配置相关例如对 k8s,docker 的配置文件的修改 |
chore | 改变构建流程、或者增加依赖库、工具等 |
revert | 回滚到上一个版本 |
如果 type
为 feat、fix
,则此次 commit 将必须出现在 change log 中。其他类型可视情况而定。
(2) scope
用于说明本次 commit 影响的范围,比如数据层、控制器层等。
(3) subject
是对 commit 的简短描述,尽量不超过 50 个字符
Body 是对本次 commit 的详细描述,可以分成多行。例如:
1 | 如有必要,更详细的解释性文本。 把它包裹起来 |
注意点:
Footer 只适用于两种情况。
(1) 不兼容变动
如果当前代码与上一个版本不兼容,则 Footer 部分以 BREAKING CHANGE
开头,后面是对变动的描述,以及变动理由和迁移方案。
1 | BREAKING CHANGE: isolate scope bindings definition has changed. |
(2) 关闭 Issue
如果当前 commit 针对某个 issue,那么可以在 Footer 部分关闭这个 issue 。
1 | Closes #123 |
还有一种特殊情况,如果当前 commit 用于撤销以前的 commit,则必须以 revert:
开头,后面紧跟被撤销的 Commit 的 Header。
1 | revert: feat(pencil): add 'graphiteWidth' option |
Body 部分的格式是固定的,必须写成 This reverts commit <hash>.
,其中的 hash
是被撤销 commit 的 SHA 标识符
如果当前 commit 与被撤销的 commit,在同一个发布(release)里面,那么它们都不会出现在 Change log 里面。如果两者在不同的发布,那么当前 commit,会出现在 Change log 的 Reverts
小标题下面。
Commitizen 是一个辅助编写 Commit message 的工具。
安装命令 npm install -g commitizen
然后在项目目录里运行
1 | commitizen init cz-conventional-changelog --save --save-exact |
以后凡是用到 git commit
的地方可以全部使用 git cz
来代替。
注意此工具会在你的项目根目录中初始化 node 模块。如果不是 node 类的项目会造成项目干扰。
插件主页:https://plugins.jetbrains.com/plugin/9861-git-commit-template
插件使用截图:
如果你的所有 Commit 都符合 Angular 格式,那么发布新版本时, Change log 就可以用脚本自动生成。
生成的文档包括以下三个部分。
每个部分都会罗列相关的 commit ,并且有指向这些 commit 的链接。当然,生成的文档允许手动修改,所以发布前,你还可以添加其他内容。
conventional-changelog 就是生成 Change log 的工具,运行下面的命令即可。
1 | npm install -g conventional-changelog-cli |
上面命令不会覆盖以前的 Change log,只会在 CHANGELOG.md
的头部加上自从上次发布以来的变动。
如果你想生成所有发布的 Change log,要改为运行下面的命令。
1 | conventional-changelog -p angular -i CHANGELOG.md -w -r 0 |
为了方便使用,可以将其写入 package.json
的 scripts
字段。
1 | { |
以后可以直接运行下面的命令
1 | npm run changelog |
当你推送(push)到公共分支后,你就不该对其变基,因为这样会很难跟进你正在改进的内容,很难跟进测试结果是什么,而且它打破了 cherry-picking。有时我们在审查流程的后期要求代码贡献者合并及变基(
git merge --squash
),以使一些东西容易还原时,我们也会违背这一规则。但一般而言,准则是:代码应干净,修改历史应真实。
但在 MySQL 设计中是否要使用外键约束是一个有争议的话题,不同的人和团队可能会有不同的看法。这里给出一些关于使用外键约束的优缺点和适用场景,以供参考:
数据完整性保障:外键约束可以保证在关联表中的数据一致性和完整性,防止了不合法的数据操作。
方便管理:外键约束可以方便地进行数据库管理,能够减少因为数据不一致而产生的问题,提高数据管理的效率和质量。
提高性能:外键约束可以避免数据冗余和冗余索引,减少数据库的存储空间,提高数据库的查询性能。
可能会影响性能:外键约束需要数据库额外的计算和检查,可能会影响数据库的性能,特别是在大型数据库中。
可能会增加复杂性:外键约束的设计需要考虑多个表之间的关系,可能会增加数据库设计的复杂性,需要更多的时间和精力。
在 MySQL 设计中是否使用外键约束,这个问题并不是一个简单的问题,而是需要根据具体情况进行综合考虑的。一般来说,以下几点是需要考虑的:
数据完整性要求:如果需要确保数据的完整性,避免数据冗余和错误,那么就需要使用外键约束。比如,如果有两个表 A 和 B,其中表 B 的一个列是表 A 的主键或唯一键,那么在表 B 中就可以定义外键约束,这样就能保证表 B 中的该列的值必须是表 A 中对应列的值,从而避免数据冗余和错误。
数据访问效率要求:使用外键约束可能会影响数据访问效率。因为在执行插入、更新或删除操作时,需要先检查外键约束是否满足,如果不满足,则不能执行该操作。如果表的数据量非常大,外键约束可能会造成一定的性能问题。因此,在设计数据库时需要综合考虑数据的完整性和访问效率的平衡。
数据库架构设计的复杂度:使用外键约束可能会增加数据库架构设计的复杂度。因为在定义外键约束时,需要考虑各种约束条件和级联操作,这可能会增加数据库设计和维护的难度。如果数据库的结构比较简单,可以不使用外键约束。
在 RabbitMQ 中,消息确认机制是防止消息丢失的关键。当生产者将消息发送到 RabbitMQ 服务器时,服务器会向生产者发送确认消息,表示已经收到了消息。如果生产者收到了确认消息,则表示消息已经被成功发送到 RabbitMQ 服务器。如果服务器在一段时间内没有收到确认消息,则会认为消息发送失败,并将消息重新发送给 RabbitMQ 服务器。这个过程会一直持续,直到消息被成功发送为止。
在消息被成功发送到 RabbitMQ 服务器后,消费者会从队列中获取消息并进行处理。如果消息处理成功,则消费者会向 RabbitMQ 服务器发送确认消息。服务器在接收到确认消息后,将会将消息从队列中删除。如果消费者在一段时间内没有发送确认消息,则 RabbitMQ 服务器会认为消息处理失败,并将消息重新发送给队列。
通过这种消息确认机制,RabbitMQ 能够保证消息在生产者和消费者之间的可靠传递。
RabbitMQ 的消息确认机制是实现可靠消息传递的重要组成部分。通过确认机制,生产者可以确认消息已经被成功发送到 RabbitMQ 服务器,消费者可以确认消息已经被成功处理,同时可以防止消息在传递过程中的丢失或重复传递。
RabbitMQ 的确认机制包括生产者确认和消费者确认。
在 RabbitMQ 中,有两种生产者确认模式,分别是事务模式和确认模式。其中,确认模式是应用程序中最常用的模式。
在事务模式下,生产者将消息发送到 RabbitMQ 服务器之前,会开启一个事务,并在事务中将消息发送到 RabbitMQ 服务器。当 RabbitMQ 服务器接收到消息后,会将消息写入到磁盘中,并将事务标记为未提交。如果生产者在一定时间内没有提交事务,则 RabbitMQ 服务器会将事务标记为已回滚,并且将消息删除。
事务模式虽然能够保证消息的可靠传递,但是由于需要对消息进行写入磁盘等操作,因此会对系统性能产生较大的影响。
在确认模式下,生产者发送消息到 RabbitMQ 服务器后,服务器会立即返回一个确认消息(acknowledgement),表示已经接收到了消息。生产者在接收到确认消息后,就可以将消息从内存中删除了。
在确认模式下,还有一些特殊的确认模式,包括事务确认模式、批量确认模式和异步确认模式等。这些模式都是基于确认模式进行扩展的,能够更好地满足不同场景下的需求。
消费者确认是 RabbitMQ 中保证消息被成功处理的重要机制。当消费者从队列中获取到消息时,必须将消息处理完毕,并将确认消息发送给 RabbitMQ 服务器。如果消费者在一定时间内没有发送确认消息,则 RabbitMQ 服务器会认为消息处理失败,并将消息重新发送给队列。
在 RabbitMQ 中,有两种消费者确认模式,分别是自动确认模式和手动确认模式。
在自动确认模式下,消费者在处理完消息后,会自动发送确认消息给 RabbitMQ 服务器。这种模式下,消费者没有任何手动确认消息的操作,因此容易出现消息丢失的情况。在处理批量消息时,自动确认模式也容易出现消息重复的情况。
在手动确认模式下,消费者必须手动发送确认消息给 RabbitMQ 服务器,以表示已经成功处理了消息。手动确认模式分为两种,分别是单条确认和批量确认。
在手动确认模式下,消费者需要在代码中显式地调用确认函数,以便正确地处理确认消息。如果消费者在处理消息时发生错误,可以使用 Nack 函数来将消息发送回队列中重新处理。
在单条确认模式下,消费者需要在处理完一条消息后,手动发送确认消息给 RabbitMQ 服务器。如果在一定时间内没有发送确认消息,则 RabbitMQ 服务器会认为消息处理失败,并将消息重新发送给队列。
在批量确认模式下,消费者需要在处理完一批消息后,手动发送确认消息给 RabbitMQ 服务器。这种模式下,消费者需要注意批量确认的最大值,以避免在确认时出现过长的等待时间。如果批量确认的消息数量过多,可能会对系统性能产生影响。
尽管 RabbitMQ 的消息确认机制能够很好地防止消息丢失,但是在实际使用中,还是有一些问题需要注意。
默认情况下,RabbitMQ 使用自动确认模式,即在消息被消费者获取之后,自动发送确认消息给服务器。这种模式虽然方便,但是会带来消息丢失的风险。因为消费者在处理消息时,如果由于某种原因无法发送确认消息,那么这条消息就会丢失。
为了避免这种情况的发生,我们可以使用手动确认模式。在手动确认模式下,消费者必须在处理完一条消息后,显式地发送确认消息给 RabbitMQ 服务器。这种模式能够保证消息不会因为消费者的问题而丢失。
消息持久化是指将消息写入持久化存储介质,例如硬盘,而不是只存在内存中。当 RabbitMQ 服务器意外关闭时,这些消息可以被恢复,并重新发送到队列中。在消息生产者发送消息时,可以将消息的 delivery mode 设置为 2(即消息持久化),以确保消息被写入磁盘。
默认情况下,RabbitMQ 会将消息保存在内存中。如果服务器宕机或者重启,内存中的消息将会丢失。为了避免这种情况的发生,我们可以将消息设置为持久化。
在消息消费过程中,如果消费者的并发数过高,可能会导致消息重复消费或者丢失。因此,我们可以通过限制消费者的并发数来降低这种风险。在 RabbitMQ 中,可以通过设置 channel.basicQos 方法的 prefetchCount 参数来实现限制消费者并发数的功能。
消息 TTL(Time To Live)是指消息的存活时间。在 RabbitMQ 中,可以为消息设置 TTL,当消息的存活时间超过指定时间后,消息将被自动删除。在设置 TTL 时,可以通过设置队列或者消息的属性来控制消息的存活时间。通过使用 TTL 机制,可以避免因为消息在队列中滞留过久而导致消息丢失的问题。
在 RabbitMQ 中,可以通过镜像队列(Mirrored Queue)的方式实现队列数据的备份。镜像队列是指在不同的 RabbitMQ 节点上同时创建相同的队列,并通过网络同步数据,以保证队列数据的高可用性和容错性。通过镜像队列的方式,可以在 RabbitMQ 节点宕机时,自动切换到其他节点,从而避免消息丢失的问题。
综上所述,以上是几种常见的预防消息丢失的方法,但是在实际应用中,还需要根据具体场景进行综合考虑和优化。例如,在一些高可用性和高并发的场景下,可能需要使用分布式事务机制,以确保消息的可靠性和一致性。此外,对于一些对消息实时性要求比较高的场景,还可以使用消息缓存、消息推送等技术来保证消息的及时性和可靠性。
总之,对于消息系统来说,预防消息丢失是一个非常重要的问题,需要采取多种手段进行保障。在使用 RabbitMQ 时,需要了解并熟练掌握其确认机制,同时也需要结合具体场景,综合考虑和优化,从而最大限度地保证消息的可靠性和一致性。
在 RabbitMQ 中,为了保证消息的可靠性和一致性,我们需要采取多种预防消息丢失的方法。其中,比较重要的一种方法是确认机制,通过确认机制,消费者可以向 RabbitMQ 服务器确认已经接收到消息,并将消息标记为已处理,从而避免消息重复消费或者丢失的问题。
除了确认机制,还有其他一些预防消息丢失的方法,例如消息持久化、限制消费者并发数、使用 TTL 机制、数据备份等。这些方法可以根据具体场景进行综合考虑和优化,从而最大限度地保证消息的可靠性和一致性。
在实际应用中,预防消息丢失是一个非常重要的问题,需要在消息系统的设计和实现中充分考虑,并采取多种手段进行保障。只有这样,才能确保消息系统的高可用性、高性能和高可靠性。
对于纯静态化部署的博客站点来说,一般写作时使用 markdown 来编写文章,当面面临需要使用图片的时候就会比较难办,一般有两种方式来解决此问题。
方案一:最简单的方法就是将图片等媒体资源与站点文件耦合到一起,此方案虽然简单,但是使用起来比较麻烦引用图片的时候需要解决复杂的路径问题,而当博客部署的时候图片资源也会挤占我们有限的静态部署空间和网络带宽,体验不是太好。
方案二:另一种方案则是实用图床将需要使用到的图片上传到一个统一的地方进行管理,在博客中直接插入图片在图床中的 URL 链接即可,此方案优点就是方便快捷并且节省静态服务的空间和网络资源,但是此方案的问题就是我们需要找到一个地方来托管我们的图片,对于纯静态无服务端的博客来说再弄个服务器来部署图片资源显然不太可能,那么图床的使用场景就应运而生,但是目前好用的图床并不多想找到一个靠谱的图床不容易,试想下突然有一天你所用的图床突然停止服务了,那么你所有的图片就都没了,这种代价应该是谁都无法接受。
因此我此前在寻找图床过程中只有两个标准,1稳定可控、2访问速度,所以收费还是付费的我都测试过不少,但最终好多图床在第一条上都无法通过,因为没有控制权,如果哪天不能访问的话我们连把图片备份下来的机会都没有,然后基于这个因素我之后测试了实用 git 托管的方式来解决这个问题,后来考虑到访问速度因素,曾经有段时间我曾使用过 gitee 来托管图片,但是最近突如其来的审查机制导致这个方案也没法用了。
这个方案的的思路其实跟 gitee 几乎是一样的,都是使用 git 托管图片文件,因此我很快就将 gitee 上的文件迁移到了 github 因为它的的稳定性和可控性应该是最好的,但是 github 唯一的问题就是他的访问速度因为一些未知因素导致有些地方访问 raw.githubusercontent.com 域名下的文件会很慢甚至无法打开,因此在使用这个方案来托管了文件之后还要使用一个 CDN 来解决访问速度问题,目前做的比较好的就是 JsDeliver 的 CDN, 做前端的同学应该不陌生,它不但提供了很多 npm 以及 js 文件的加速,而且它还提供了 github 和 wordpress 的加速服务,并且使用简单,只需要替换 CDN 域名即可到达加速效果。
例如加速 github 文件只需要将 github 的文件地址转换成如下地址即可:
https://cdn.jsdelivr.net/gh/你的用户名/你的仓库名@发布的版本号/文件路径
其中 @发布的版本号
是非必需的,如果不带默认取的是仓库主分支的最新文件。
创建一个可公开访问的 Github 仓库
例如下图所示创建一个名为 img-bed
的仓库。(名称可以随便取,自己开心就好😄)
上传文件
然后用你自己知道的方式上传文件即可。例如我的仓库中传入的文件如下
更换 URL 链接使用
上传完成后,获取到文件的链接,然后按照方案介绍中的方式将链接更换为 CDN 加速链接即可,例如
原文件地址:https://raw.githubusercontent.com/JasonYHZ/CDN/master/blog/202211271244496.webp
更换地址后:https://cdn.jsdelivr.net/gh/JasonYHZ/CDN/blog/202211271244496.webp
以上步骤就是这个方案的全部使用过程,虽然过程比较简单,但也比较繁琐,如果写一篇文章有十几张图片,光是处理这个路径就要麻烦的要死,因此后面会介绍工具来自动帮助我们完成这些操作,甚至还可以自动帮我们压缩图片,添加水印。
PicGo 官网 https://picgo.github.io/PicGo-Doc/zh/guide/,它是一个多平台的可用于快速上传图片并获取 URL 链接的工具。更详细的使用说明可以参考官方说明文档。这里直接切入主题介绍下上传设置,水印处理,和压缩等操作设置。
PicGo 支持多种上传,这里我们使用 github 的图床设置,如图。
没什么特别复杂的东西只是需要申请一个 token 来访问 github 仓库即可,创建 token 地址: https://github.com/settings/tokens
点击 Generate new token
选择 Generate new token (classic)
然后把 repo 的权限勾选即可,过期时间可以设置成永不过期。
然后下滑到最底部,点击 Generate token
按钮。然后回到了前一个页面并显示刚刚创建的 Token
这个 token 生成后只会显示一次,要及时复制备份。
此时就可以是用这个工具来上传图片了,至于其他使用设置,请看官方文档的说明。
PicGo 可以安装插件来自动对你的图片设置水印。我使用的是在线安装的方式来安装的,需要有 Node.js
的环境支持,如果你无法使用在线方式,可以参阅官方文档的其他方式进行安装
另外使用在线安装的方式是使用 Npm
来安装插件的,如果遇到下载问题可以设置插件下载代理或者 NPM
下载镜像。
安装后设置一下就可以了
更多说明请参阅插件仓库的说明文档
设置压缩方式,其他方式请参阅插件仓库的说明文档
设置完成后需要开启。
本文针对于开发者或者是想去了解入门 Kubernetes 的读者,以快速上手为目标,将 Kubernetes 的核心的一些概念尽量简短快速的做一个介绍,因为 Kubernetes 的内容非常的庞大且繁琐(对的就是繁琐并不是复杂,因为它的概念太多了对新手来说不知道从哪里入手),所以这也是我写这篇文章的原因,其实 Kubernetes 学习起来并不复杂,只要先了解了最基础的一些核心概念,然后直接上手实践就是最好的学习路径,因此才有了这个入门系列的文章,最终目标是可以让想要入门学习 Kubernetes 的开发者、运维人员等 IT 从业者用最短的时间将 Kubernetes 这个好玩的玩具玩起来。
要学习它还是有些前提基础要掌握的不过比较简单,需要读者们掌握 Linux、Docker 即可。
当然学习它之前还是需要将一些概念掌握清楚才可以,这也是必不可少的,当然也是比较枯燥的,而且也是困住新手的第一大关,过不了这一关就没办法上手去玩它就永远没办法学会它。不过放心我并不是想做一个大头书文档,而是用一些通俗易懂的解释将一些比较关键的概念解释清楚,目的就是玩它!
此外还要声明一下如果有了基础的同学,之后再学习 Kubernetes 最好的办法、最好的资料就是官方文档,没有之一。
在学习一门技术之前我们不妨先来了解下它的出身历史,以便更好的去了解它学习它。
近几年 Kubernetes 这个词被炒的热火朝天,有稍微了解过的同学可能知道它是 Google 公司于2014年6月份将内部的一套容器编排调度引擎 Borg 系统改造后开源来的,因此它的核心就是针对容器的编排调度,所以给它起了一个特别形象的名字 “Kubernetes” 希腊语里意为 “舵手”、“飞行员”或“州长”之类的以及控制类的词的根源,然后由于这个单词 “K” 和 “s” 之间有8个字母所以简称就叫了 “K8s”。下面来看下一个形象的图加深下对它的理解。
由于前文提到 Kubernetes 的核心就是针对容器的编排调度,所以我们这里也大概介绍下什么是容器。
其实容器技术很早就已经出现了,最早是在1979年就在 Unix v7 系统中得到了支持,一致发展到2013年随着 Docker 的诞生才把容器化推向了风口。
容器指的是系统虚拟化的一种形式,有点类似虚拟机,有自己的文件系统、CPU、内存、进程空间等。
但是它比虚拟机更轻量化,因为各容器之间共享一个操作系统内核,容器使用操作系统的虚拟化形式,利用操作系统的功能(例如Linux内核中的名称空间和cgroups)来隔离进程,并控制这些进程可以使用的 CPU、内存和磁盘的数量,因此容器它与虚拟机不同,容器不需要在每次启动时都包含一个完整的操作系统,它只需要利用主机操作系统的功能和资源即可。
目前我们使用最多的应该就是 Docker 这个容器,其实并不是只有 Docker,因为在 2015年6月22日的时候 Docker 公司就将他们的 libcontainer 技术开源,并联合 CoreOS、Google、RedHat 等公司一起将此技术改名为成立了 RunC 项目,并交由中立的开源基金会管理,并以 RunC 为基础,共同制定了一套容器和镜像的标准和规范,称为 OCI:Open Container Initiative。
所以目前除了Docker之外还有一些基于 RunC 的容器技术例如 Podman、CRI-O、Containerd等。
聊完容器之后还要在了解一个概念叫容器化,容器只是一个环境,能够为程序提供一个类似于虚拟机但比虚拟机更高效的运行环境,因此容器化其实就是将原先的应用程序所需要的所有内容,包括编译后的二进制文件、依赖关系、库和配置文件等等,全部都封装进容器之中,然后可以针对起容器做一些基础资源的访问限制,例如可用的cpu、内存、存储等资源,类似于一个轻量化的虚拟机,然后容器化过的应用可以无视任何操作系统架构和环境,都能直接运行容器化应用而不需要重复的编译构建,解决运行环境依赖等问题。
在继续深入了解 Kubernetes 其他的概念之前,先来介绍下 Kubernetes 的总体架构,先来认识个全貌再来介绍具体内容。
我们先来看一张完整的 Kubernetes 的集群图。
Kubernetes 是一个生产级别的开源平台,可以协调高可用的计算机集群,它可以协调在集群内或者夸集群的应用容器的部署调度和执行。
每个计算机作为独立的单元相互连接到集群中并由 Kubernetes 进行控制管理, Kubernetes 中的抽象允许你将容器化的应用部署到集群,而无需将它们绑定到某个特定的独立计算机。
为了使用这种新的部署模型,应用需要以将应用与单个主机分离的方式打包:因此它们需要被容器化。与过去的那种应用直接以包的方式深度与主机集成的部署模型相比,容器化应用更灵活、更可用。
如图中所示,一个 Kubernetes 集群中主要包含两种类型的资源:
Master 协调集群中的所有活动,例如调度应用、维护应用的所需状态、应用扩容以及推出新的更新。
它在 Kubernetes 集群中充当工作机器的角色 每个Node都有 Kubelet , 它管理 Node 而且是 Node 与 Master 通信的代理。
Node 还应该具有用于处理容器操作的工具,例如 Docker。
在 Kubernetes 上部署应用时,你告诉 Master 启动应用容器。 Master 就编排容器在集群的 Node 上运行。** Node 使用 Master 暴露的 Kubernetes API 与 Master 通信。**终端用户也可以使用 Kubernetes API 与集群交互。
接着我们来了解一下工作节点 ** Node **中都有什么,为此我们来看一下这张图
可以看到 Node 节点上只有一种资源类型,那就是 Pod,它是 Kubernetes 中创建和管理的、最小的可部署的计算单元。
Pod 是 Kubernetes 抽象出来的,表示一组一个或多个应用程序容器(如 Docker),以及这些容器的一些共享资源。这些资源包括:
除了应用容器,Pod 还可以包含在 Pod 启动期间运行的 Init 容器。 你也可以在集群中支持临时性容器 的情况下,为调试的目的注入临时性容器。
一个 pod 总是运行在 工作节点。工作节点是 Kubernetes 中的参与计算的机器,可以是虚拟机或物理计算机,具体取决于集群。每个工作节点由主节点管理。工作节点可以有多个 pod 。
Kubernetes 主节点会自动处理在集群中的工作节点上调度 pod 。 主节点的自动调度考量了每个工作节点上的可用资源。
每个 Kubernetes 工作节点至少运行:
当你部署完 Kubernetes,即拥有了一个完整的集群。
一个 Kubernetes 集群由一组被称作节点的机器组成。这些节点上运行 Kubernetes 所管理的容器化应用。集群具有至少一个工作节点。
工作节点托管作为应用负载的组件的 Pod 。控制平面管理集群中的工作节点和 Pod 。 为集群提供故障转移和高可用性,这些控制平面一般跨多主机运行,集群跨多个节点运行。
现在我们来了解下交付正常运行的 Kubernetes 集群所需的各种组件。
** 控制平面组件(Control Plane Components) **
控制平面的组件对集群做出全局决策(比如调度),以及检测和响应集群事件(例如,当不满足部署的 replicas 字段时,启动新的 pod)。
控制平面组件可以在集群中的任何节点上运行。 然而,为了简单起见,设置脚本通常会在同一个计算机上启动所有控制平面组件, 并且不会在此计算机上运行用户容器。 请参阅使用 kubeadm 构建高可用性集群 中关于跨多机器控制平面设置的示例。
kube-apiserver
API 服务器是 Kubernetes 控制面的组件, 该组件公开了 Kubernetes API。 API 服务器是 Kubernetes 控制面的前端。
Kubernetes API 服务器的主要实现是 kube-apiserver。 kube-apiserver 设计上考虑了水平伸缩,也就是说,它可通过部署多个实例进行伸缩。 你可以运行 kube-apiserver 的多个实例,并在这些实例之间平衡流量。
etcd
etcd 是兼具一致性和高可用性的键值数据库,可以作为保存 Kubernetes 所有集群数据的后台数据库。
你的 Kubernetes 集群的 etcd 数据库通常需要有个备份计划。
kube-scheduler
控制平面组件,负责监视新创建的、未指定运行节点(node)的 Pods,选择节点让 Pod 在上面运行。
调度决策考虑的因素包括单个 Pod 和 Pod 集合的资源需求、硬件/软件/策略约束、亲和性和反亲和性规范、数据位置、工作负载间的干扰和最后时限。
kube-controller-manager
运行控制器进程的控制平面组件。
从逻辑上讲,每个控制器都是一个单独的进程, 但是为了降低复杂性,它们都被编译到同一个可执行文件,并在一个进程中运行。
这些控制器包括:
** cloud-controller-manager **
云控制器管理器是指嵌入特定云的控制逻辑的 控制平面组件。 一般是云厂商来使用。
工作负载是在 Kubernetes 上运行的应用程序。
无论你的负载是单一组件还是由多个一同工作的组件构成,在 Kubernetes 中你 可以在一组 Pods 中运行它。 在 Kubernetes 中,Pod 代表的是集群上处于运行状态的一组 容器。
Kubernetes Pods 有确定的生命周期。 例如,当某 Pod 在你的集群中运行时,Pod 运行所在的 节点 出现致命错误时, 所有该节点上的 Pods 都会失败。Kubernetes 将这类失败视为最终状态: 即使该节点后来恢复正常运行,你也需要创建新的 Pod 来恢复应用。
不过,为了让用户的日子略微好过一些,你并不需要直接管理每个 Pod。 相反,你可以使用 负载资源 来替你管理一组 Pods。 这些资源配置 控制器 来确保合适类型的、处于运行状态的 Pod 个数是正确的,与你所指定的状态相一致。
Pod 通常不是直接创建的,而是使用工作负载资源创建的。 有关如何将 Pod 用于工作负载资源的更多信息,请参阅 使用 Pod。
下面我们来讲 Kubernetes 内置的工作负载。
Deployment 非常适合管理集群上的无状态应用,Deployment 控制的所有 Pod 都是互相等价的,并可以在任何时候去动态的创建和销毁。
所谓的无状应用就是指针对每次请求的处理,不依赖其他的相关服务,并且自身也不保存任何相关的信息,而且任何时间启动的服务针对同一个请求的响应都是一致的,例如 Nginx 应用。
Deployment 为 Pod 提供了可以声明式更新的能力,你只需要负责向 Deployment 描述目标的状态,然后 Deployment 控制器就以受控速率去更改实际的状态,以使其达到描述中的期望状态。
既然有无状态应用,那么就会存在有状态的应用,例如 Mysql、Redis 等应用,那么这类的应用就应该由 StatefulSet 来管理。
StatefulSet 用来管理某 Pod 集合的部署和扩缩, 并为这些 Pod 提供持久存储和持久标识符。
和 Deployment 类似, StatefulSet 管理基于相同容器规约的一组 Pod。但和 Deployment 不同的是, StatefulSet 为它们的每个 Pod 维护了一个有粘性的 ID。这些 Pod 是基于相同的规约来创建的, 但是不能相互替换:无论怎么调度,每个 Pod 都有一个永久不变的 ID。
StatefulSets 对于需要满足以下一个或多个需求的应用程序很有价值:
在上面描述中,“稳定的”意味着 Pod 调度或重调度的整个过程是有持久性的。 如果应用程序不需要任何稳定的标识符或有序的部署、删除或伸缩,则应该使用 由一组无状态的副本控制器提供的工作负载来部署应用程序,例如 Deployment。
由于 Deployment 是可能在同一个节点上部署同一个 Pod 多次的,但是有时候我们只希望我们的某个应用在每个节点上都只有一个 Pod 副本在执行,那就可以使用 DaemonSet 来创建 Pod。
DaemonSet 确保全部(或者某些)节点上运行一个 Pod 的副本。 当有节点加入集群时, 也会为他们新增一个 Pod 。 当有节点从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod。
DaemonSet 的一些典型用法:
一种简单的用法是为每种类型的守护进程在所有的节点上都启动一个 DaemonSet。 一个稍微复杂的用法是为同一种守护进程部署多个 DaemonSet;每个具有不同的标志, 并且对不同硬件类型具有不同的内存、CPU 要求。
还有一种情况就是比如我们需要执行一些一次性的任务,或者以某种指定的频率周期来定期的执行某些任务,那么就可以使用 Job 和 Cron Job 来管理我们的 Pod。
Job 会创建一个或者多个 Pods,并将继续重试 Pods 的执行,直到指定数量的 Pods 成功终止。随着 Pods 成功结束,Job 跟踪记录成功完成的 Pods 个数。 当数量达到指定的成功个数阈值时,任务(即 Job)结束。 删除 Job 的操作会清除所创建的全部 Pods。
一种简单的使用场景下,你会创建一个 Job 对象以便以一种可靠的方式运行某 Pod 直到完成。 当第一个 Pod 失败或者被删除(比如因为节点硬件失效或者重启)时,Job 对象会启动一个新的 Pod。
你也可以使用 Job 以并行的方式运行多个 Pod。
Cron Job 就是可以周期性重复的 ** Job, **可以参考下 Linux 中的 Crontab。
Caution:
所有 CronJob 的 schedule: 时间都是基于 kube-controller-manager. 的时区。
如果你的控制平面在 Pod 或是裸容器中运行了 kube-controller-manager, 那么为该容器所设置的时区将会决定 Cron Job 的控制器所使用的时区。
我们部署的应用最终都是运行在集群 Node 中的 Pod 里,如果我们要访问我们自己的应用就不可避免的要考虑如何访问 Pod。
在 Kubernetes 中每一个 Pod 都有它自己的IP地址, 这就意味着你不需要显式地在 Pod 之间创建链接, 你几乎不需要处理容器端口到主机端口之间的映射。 这将形成一个干净的、向后兼容的模型;在这个模型里,从端口分配、命名、服务发现、 负载均衡、应用配置和迁移的角度来看, Pod 可以被视作虚拟机或者物理主机。
所以简单的来说你可以通过通过 Node 本身进行请求端口转发,将请求转发到你的 Pod上就可以访问到我们的应用了,但是这时一个很不推荐的做法,因为 Pod 本身不知道 Node 的端口是什么,另外 Pod 有可能会销毁和重建,每一次销毁重建,Pod 的网络 IP 都会发生变化,因此如果通过这种方式来访问应用,就又丧失了 Kubernetes 的意义了。
将运行在一组 Pods 上的应用程序公开为网络服务的抽象方法。
使用 Kubernetes,你无需修改应用程序即可使用不熟悉的服务发现机制。 Kubernetes 为 Pods 提供自己的 IP 地址,并为一组 Pod 提供相同的 DNS 名, 并且可以在它们之间进行负载均衡。
因为 Kubernetes 会动态的来创建和销毁 Pod 以匹配集群的状态,因此 Pod 不是固定不变的,这导致了一个问题: 如果一组 Pod(称为“后端”)为集群内的其他 Pod(称为“前端”)提供功能, 那么前端如何找出并跟踪要连接的 IP 地址,以便前端可以使用提供工作负载的后端部分?
因此 Kubernetes 抽象了一种资源叫 Service 资源:逻辑上的一组 Pod,一种可以访问它们的策略 —— 通常称为微服务。Service 所针对的 Pods 集合通常是通过选择算符来确定的
举个例子,考虑一个图片处理后端,它运行了 3 个副本。这些副本是可互换的 —— 前端不需要关心它们调用了哪个后端副本。 然而组成这一组后端程序的 Pod 实际上可能会发生变化, 前端客户端不应该也没必要知道,而且也不需要跟踪这一组后端的状态。
Service 定义的抽象能够解耦这种关联。
Service 匹配一组 Pod 是使用 标签(Label)和选择器(Selector), 它们是允许对 Kubernetes 中的对象进行逻辑操作的一种分组原语。标签(Label)是附加在对象上的键/值对,可以以多种方式使用:
标签(Label)可以在创建时或之后附加到对象上。他们可以随时被修改。现在使用 Service 发布我们的应用程序并添加一些 Label 。
Kubernetes 集群本身并不为你处理数据的存储,在容器中的文件在磁盘上都是临时存放的,这些数据会随着容器的销毁而一起消失。另外在同一个 Pod 中运行的多个容器也会出现多个容器需要共享文件。
为此 Kubernetes 提供了 Volume 卷这一概念来解决这两个问题。
在 Docker 中也有 Volume 卷的概念,但对它只有少量且松散的管理。 Docker 卷是磁盘上或者另外一个容器内的一个目录。
Kubernetes 支持很多类型的卷。 Pod 可以同时使用任意数目的卷类型。 临时卷类型的生命周期与 Pod 相同,但持久卷可以比 Pod 的存活期长。 当 Pod 不再存在时,Kubernetes 也会销毁临时卷;不过 Kubernetes 不会销毁持久卷。 对于给定 Pod 中任何类型的卷,在容器重启期间数据都不会丢失。
卷的核心是一个目录,其中可能存有数据,Pod 中的容器可以访问该目录中的数据。 所采用的特定的卷类型将决定该目录如何形成的、使用何种介质保存数据以及目录中存放的内容。
Kuberneters 的 Volume 类型非常的丰富,在日常使用中用到最多的是下面几个类型:
当 Pod 分派到某个 Node 上时,emptyDir 卷会被创建,并且在 Pod 在该节点上运行期间,卷一直存在。当 Pod 因为某些原因被从节点上删除时,emptyDir 卷中的数据也会被永久删除。
此卷一般是用来当作临时存储使用
特殊的存储类型,这是向 Pod 提供配置文件和加密文件的主要方式,configMap 卷提供了向 Pod 注入配置数据的方法。 ConfigMap 对象中存储的数据可以被 configMap 类型的卷引用,然后被 Pod 中运行的容器化应用使用
secret 卷用来给 Pod 传递敏感信息,例如密码。你可以将 Secret 存储在 Kubernetes API 服务器上,然后以文件的形式挂在到 Pod 中,无需直接与 Kubernetes 耦合。
Kubernetes 的持久化存储卷,用来将持久卷(PersistentVolume 简称 PV)挂载到 Pod 中。
对于持久卷的申请(PersitentVolumeClaim 简称 PVC)是在用户无感知的环境下由云厂商来处理实现的。
目前关于持久化类型里在集群中使用最多的其实是动态的卷供应,由集群管理员来制定存储设备的规格,然后由云厂商来动态的提供存储设备。
动态卷供应允许按需创建存储卷。 如果没有动态供应,集群管理员必须手动地联系他们的云或存储提供商来创建新的存储卷, 然后在 Kubernetes 集群创建 PersistentVolume对象来表示这些卷。 动态供应功能消除了集群管理员预先配置存储的需要。 相反,它在用户请求时自动供应存储。
这个章节的知识点比较复杂,这里推荐阅读以下资料
https://support.huaweicloud.com/basics-cce/kubernetes_0030.html
https://kubernetes.io/zh/docs/concepts/storage/dynamic-provisioning/
ConfigMap 是一种 API 对象,用来将非机密性的数据保存到键值对中。使用时, Pods 可以将其用作环境变量、命令行参数或者存储卷中的配置文件。
ConfigMap 将你的环境配置信息和 容器镜像 解耦,便于应用配置的修改。
比如,假设你正在开发一个应用,它可以在你自己的电脑上(用于开发)和在云上 (用于实际流量)运行。 你的代码里有一段是用于查看环境变量 DATABASE_HOST,在本地运行时, 你将这个变量设置为 localhost,在云上,你将其设置为引用 Kubernetes 集群中的 公开数据库组件的 服务。
这让你可以获取在云中运行的容器镜像,并且如果有需要的话,在本地调试完全相同的代码。
ConfigMap 在设计上不是用来保存大量数据的。在 ConfigMap 中保存的数据不可超过 1 MiB。如果你需要保存超出此尺寸限制的数据,你可能希望考虑挂载存储卷 或者使用独立的数据库或者文件服务。
Secret 是一种包含少量敏感信息例如密码、令牌或密钥的对象。 这样的信息可能会被放在 Pod 规约中或者镜像中。 使用 Secret 意味着你不需要在应用程序代码中包含机密数据。
由于创建 Secret 可以独立于使用它们的 Pod, 因此在创建、查看和编辑 Pod 的工作流程中暴露 Secret(及其数据)的风险较小。 Kubernetes 和在集群中运行的应用程序也可以对 Secret 采取额外的预防措施, 例如避免将机密数据写入非易失性存储。
Secret 类似于 ConfigMap 但专门用于保存机密数据。
Pod 可以用三种方式之一来使用 Secret:
Kubernetes 控制面也使用 Secret; 例如,引导令牌 Secret 是一种帮助自动化节点注册的机制。
本篇全都是概念性的讲解,可能会有些枯燥,但这也是必经之路没有办法绕过,后续我会更新入门操作篇,后续再会。
]]>我是一个爱钻研的小小程序员,之前一直都是看别人的文章,别人的博客,慢慢的随着自己的知识的积累,我也想弄个自己的博客,来写写自己的学习经验,分享下自己的技术等等因素吧,于是我最近一段时间就一直在琢磨这个事,这期间我看过很多的博客平台有CSDN、博客园、简书等等一系列博客平台,不得不承认这些平台很优秀,但是我最终放弃了他们选择了自己来搭个人博客,主要原因我觉得在这上面写东西一个是没有归属感,再一个觉得不够酷(zhuang)炫(bi):smirk:
于是我就开始自己琢磨怎么搭建自己的个人博客,起初最先想到的方案就是买个服务器弄个博客程序自己搭建一下,但是还是觉得繁杂,而且费用高昂,花钱花精力维护着这么一个没人访问的破站,瞬间会让自己没有坚持下去的动力,这样也违背了我的初衷。后来随着慢慢的深扒,我发现了hexo这种堪称神器的东西,瞬间满足了我的各种需求。
所以我后来就采用了这种方案,然后就有了你们现在看到的这篇文章了哈哈!好下面废话不多说了我开始进入正题!
引用hexo官网文档的介绍
Hexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。
更多基础知识可参考hexo官网,本篇文章的重点是介绍部署的技巧。如果已掌握hexo的基础知识,可直接跳到部署章节进行浏览。
安装hexo之前需要安装一下环境:
安装完node后由于npm是自带的,可能版本有些落后,需要先将自身升级一下。
1 | npm -g install npm |
由于国内互联网环境,导致npm下载失败,可使用npm淘宝镜像cnpm
1 | npm install -g cnpm --registry=https://registry.npm.taobao.org |
如遇到npm下载组件失败时,可使用cnpm进行下载,只需将npm替换为cnpm,下方代码将继续使用npm,请根据自身网络环境选择适合的使用。
以上环境如果没有问题,下面就开始安装hexo了
1 | npm install -g hexo-cli |
之后在命令行中测试输入
1 | hexo version |
安装hexo完成后,接下来我们就该使用hexo来创建自己的个人站点了。
1 | hexo init <folder> #使用hexo命令在指定的<folder>文件夹下初始化创建一个博客项目 |
新建完成后详细的目录含义以及配置教程,请参阅hexo官网文档的介绍本篇文章暂不赘述。
上面代码执行完毕之后,我们就已经创建好了一个自己的个人博客了,下面我们只需要使用hexo的命令工具生成博客并用自带的服务器模块启动,预览下我们的博客。
1 | hexo clean #清理各种缓存和旧文件 |
执行完 hexo s
后命令行窗口将提示您如下信息
1 | INFO Start processing |
其中服务器预览地址就是 http://localhost:4000/
至此,我们的个人博客创建好了,下面我们就来进入部署环节。
经过第一个章节,我们知道,当我们使用 hexo g
和 hexo s
命令生成并开启服务后,我们本地访问的测试域名实际是指向了我们当前目录下的 public 目录,也就是说 hexo g
命令生成的静态文件就是 public 目录下的文件,部署的过程就是将这个 public 目录下的文件放到我们的服务器上这样就完成了部署。
好了,介绍完了上面我们知道我们接下来是要弄到可靠的服务器来吧我们的前端代码方上去,那么我们选什么服务器呢?
由于我们是纯前端化的博客,无需运行什么后端环境,所以购买服务器就不适合我们了,而且也违背了我们的初衷(买服务器只用来跑一个静态页面,血坑啊,而且还要涉及到备案等一系列手续,黄花菜都凉了)。
那用过github的读者都知道github有个pages服务,是否可以用呢?答案是不行!原因有一下几点:
最终放弃使用github的pages服务
那么有的读者可能会想到使用coding,这里我告诉你答案仍然是不行!!最主要原因是,使用了coding后绑定了自定义域名,在每天第一次访问自己的域名到自己的博客之间,coding会插入一个广告跳转页面,非常之恶心,虽然说底部加入一个外链指向coding,然后申请提交后可取消广告跳转页面,但是谁能保证以后会不会收费,而且莫名其妙的带了个外链,反正心里不爽
好了说到这里,就该介绍我们的netlify服务了。官网:https://www.netlify.com
netlify服务避免了以上所有缺点,而且结合github分支部署已达到更快捷的部署体验。下面是我画的一个部署架构图,可以大致的先看下部署架构图。
这里可能有点绕,因为我们编辑发表文章,生成静态部署文件是用的项目代码,而真正发布的文章是项目给我们生成的 public 下的静态文件,所以我们实际上是有两份代码需要托管的,而且这两份代码实际上都是一个项目产出的,所以放到一个git仓库下更合适,然后使用git的分支隔离特性来托管我们的两份代码才是真正的hexo的正确使用方式。
通过上面的图可以看到我的仓库上是有两个分支的,一个是source分支,是用来存放我们编辑的源码,另一个是master分支是用来放我们要部署的代码的,经过以上我们的介绍,实际上我们发现我们要把我们的源代码托管到github上,还有我们编译好的静态文件也要托管到github上,然后我们的netlify服务实际上是经过webhook钩子,勾住了github上某个分支(这里是master分支),当有推送之类的更新操作时,我们的netlify会自动拉去我们仓库中的文件完成自动部署。
首先把我们的本地项目代码推送到github仓库
1 | git init #初始化本地项目仓库 |
好了至此我们已经把我们的项目代码推送到了 master 分支上了
好了我们无需切换我们的分支,下面我们需要去配置一下我们的hexo的配置文件 _config.yml
找到 deploy 选项进行如下修改
1 | deploy: |
好了我们设置完毕,再次执行下面命令我们就可以吧我们的 hexo 项目代码和静态资源推送到我们的github仓库里了。
1 | hexo clean |
如果执行 hexo d
时出错提示 ERROR Deployer not found: git
可能是没有安装部署工具执行下面代码
1 | npm install hexo-deployer-git --save |
之后再次执行 hexo d
即可,执行完毕后进入我们的github仓库中会发现多出了一个分支 run-page,正是我们在配置文件中设置的分支,这里面就是放的我们要部署的文件。
好的接下来我们进入netlify官网,然后点击 右上角的 Login
选择使用github账户授权登录,登录完成后我们就进入到了我们的控制台
然后我们选择右上角的 New site from Git
按钮,然后选择 github
然后选择要部署的项目仓库。
然后选中要部署的分支
之后点 Deploy site
按钮提交部署之后进入到网站的控制台去进行设置域名绑定和https申请即可,部署成功后会自动进行cdn加速的。
之后我们就不需要这么麻烦了以后编辑好文章之后,只需要执行 hexo clean && hexo g && hexo d
即可自动化部署,然后要记得将我们的项目文件 push 到 github 的 master分支上去哦。
如果你需要换设备或者多人进行写作那么只需要共同维护好项目分支的代码就行了,部署分支无需处理。
例如你换了新的电脑需要维护你的博客
1 | git clone xxxx.git <file> |
经过上面的简单的命令,你的项目已经能在新的设备上进行编辑了,当然要注意新的设备要安装好相应的环境。
当你编写完文章后,还是执行 hexo clean && hexo g && hexo d
即可自动部署成功!
如果觉得文章不错可以帮忙转发给您的朋友哦。
]]>博客搭建完成了,作为一个码农开篇第一帖当然是 Hello World!
此博客主要是用来记录一些日常,分享一些技术干货,有兴趣的小伙伴可以点下收藏,有友链的可以互相交换一下哦。另外我博客搭建的有评论板块,只需要填写用户名和邮箱即可在文章下方评论留言方便大家交流。
由于之前工作生活事情较多,计划中一直想把自己的博客更新起来,却一直因为各种事情被耽搁了,所以我决定把自己的博客重新搭建一下。
2022一起加油吧!
好了由于是开篇第一帖,这篇帖子就先写到这把,后面我会整理更多的内容发送出来,当然也会写一些随笔记录,日常之类的内容不嫌弃的话就看看吧。
]]>