Giter VIP home page Giter VIP logo

myblog's Introduction

Hi there 👋

我的 GitHub 数据

欢迎关注我的公众号: image-20210822153904096

myblog's People

Contributors

helios741 avatar syl741 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

myblog's Issues

浅谈前后端分离中的跨资源共享(CORS)

原文地址

简介

当下无论大厂小厂的前后端开发模式都是前后端分离。以前遇到通过jsonp解决跨域的方式也渐渐的淡出的工程中(不了解jsonp的可以看JSONP跨域请求+简答实现百度搜索)。当前端请求一个接口的时候就会引起跨域,但是当下的前端构建工具都有相应的解决方案,比如webpackweb-dev-server这个插件,就能很简单的启一个本地的服务,然后发请求的时候通过启的本地服务去发送请求,这样就解决跨域问题的一部分了。这种情况下客户端代码和正常非跨域请求一摸一样,不用做任何改变。

上面说的方法只是解决了一部分,还有一部分我想大家可能或多或少的会遇到过,就是在登录场景的时候。服务端的response里面有set-cookie这个字段,在客户端中设置cookie,cookie里面可能包含着的seesionID表示的当前登录的用户/当前的登录状态(对这方面不理解的可以看通过cookie和session让http协议变得有状态)。当用户已经登录并且访问其他页面的时候,服务端会通过cookie中的信息去校验用户登录状态,如果请求中没有携带身份信息或者身份信息过期(服务端返回401/403)就会跳转到登录界面。这种情况如果在前后端联调的时候比较麻烦,因为上面方法解决的跨域是不会携带cookie的。目前有两种方法去解决这个:

  1. 在登录之后拿到session/token每个请求都默认加上这个值(写死在代理中)
  2. 在请求中增加withCredentials,服务端要设置对应的几个响应头,但是对服务端改动比较多。

综上: CORS的主要任务都落在服务端,但是如果为了联调服务端的开发代码和生产代码有区别,他们肯定是会不搞的。

背景

今天组里的实习生在使用Axios去验证登录的时候遇到了跨域的问题(前端是vue, 后端是Spring boot)。
一般的请求通过前端设置代理,服务端设置Access-Control-Allow-origin:\* 就可以了,但是登录时候响应头里面要去set cookie就遇到了问题,结果由于对CORS跨域理解的不是很深刻,对预检请求不是很了解,就在axios的issues里面去搜,通过Axios doesn't send cookies with POST and data定位到了问题,原来他后端写的拦截器里面自动把OPTIONS这个请求给过滤掉了,没有让走到后面的流程。

什么是CORS

CORS的出现是为了解决由于浏览器的同源策略带来的请求跨域问题。

“跨资源共享(Cross-Origin Resource Sharing(CORS))是通过HTTP Response header来告诉浏览器,让运行在一个origin(domain)上的web应用被允许访问来自不同源服务器上指定的资源的一种机制。”

简单来说: CORS就是通过设置请求的响应头(能通过开发人员控制的基本都是服务端的响应头,客户端的也会有对应的请求头,但一般不会是开发人员去控制的,后面会仔细说)去控制是否允许某个origin的某个/些请求跨域。

CORS的功能

CORS标准新增了一组HTTP首部字段,允许服务端声明哪些源站通过浏览器有权访问哪些资源。对于能对服务器产生副作用的HTTP非简单请求(non-simple request)(特别是除了GET请求以外的请求),浏览器必须首先发送一个方法为OPTIONS的一个预检请求(preflight request)来获取服务器是否允许该请求跨域。服务器得到确认之后,才发起真正的HTTP请求。在预检请求中,服务端也可以通知客户端是否要携带Credentials.

CORS的三种请求

简单请求

某些请求是不会触发预检查请求的,这些请求被成为简单请求(simple request)。
如果一个请求满足下列的所有条件就可以被称为简单请求:

  1. 使用下列的方法之一:
  • GET
  • HEAD
  • POST
  1. 除了浏览器自动设置的头,只能设置Fetch 规范允许设置的“CORS安全请求头
  1. Content-Type的值只限于下面几个(注意没有application/json,现在post的请求经常使用这个,所以当发post请求的时候会触发预检请求不要意外):
  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain
  1. 请求中的任意XMLHttpRequestUpload对象均没有注册任何时间监听器。XMLHttpRequestUpload对象可以使用*XMLHttpRequest.upload *属性访问。
  2. 请求中没有使用ReadableStream对象

预检请求

不满足上面定义的简单请求,都会发送预检请求。

比如浏览器要发送一个POST请求,content-Typeapplication/json,新增一个request header为X-TEST

预检请求的步骤:

  1. 发送一个method为OPTIONS的请求,这个请求的目的是
  • 向服务器请求是否支持实际请求发送的方法(是否支持post方法)
  • 向服务器请求是否支持实际请求新增的header或者不满足简单请求的header
  1. 如果服务器接受并正确返回就发送实际的post,并且能带上相应的header

携带credentials的请求

对于跨域(发生CORS)的请求默认是不会带上凭证信息(credentials)的,如果要发送凭证信息(credentials)就需要设置对应的标识位。

请求:

  • 请求中要设置withCredentials为true。

响应:

  • Access-Control-Allow-Credentials: true
  • Access-Control-Allow-Origin的值不再是通配符*,应该是单一的origin。

HTTP规范规定Access-Control-Allow-Origin不能是通配符*并且只能是单一的origin。这是因为如果能设置多个的话,证明该服务器就能接受多个域名下面的cookie,这是很危险的。

CORS中的请求头和响应头

响应头

Access-Control-Allow-Origin

Access-Control-Allow-Origin: <origin> | *

origin参数的值制定了允许访问服务器资源的外域URI。对于不需要携带身份凭证的请求,服务器可以指定这个字段的值为通配符*,表示允许来自所有域的请求。

Access-Control-Expose-Headers

该头信息服务器把允许浏览器访问的头放入白名单,例如:

Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header
在跨域访问的时候,XHR对象的getResponseHeader()只能拿到一些最基本的响应头。

Access-Control-Max-Age

指定了预检请求(preflight)请求的结果能被缓存多久(秒为单位)。

Access-Control-Allow-Credentials

当浏览器的credentials设置为true时,是否允许浏览器读取response的内容

Access-Control-Allow-Methods

作为预检请求的响应头,指明了实际请求所允许的HTTP方法。

Access-Control-Allow-Headers

用于预检请求的响应。其指明了实际请求中允许携带的首部字段。
以逗号分割。

请求头

这些字段一般无需手动设置。

Origin

预检请求或实际请求的源站。

不包含任何路径,只是服务器的名称。(不管是否为跨域,这个字段都被发送。)

Access-Control-Request-Method

用于预检请求。其作用是,将实际请求所使用的 HTTP 方法告诉服务器。

参考

高烧后的思考

昨天晚上高烧,自己一个人在住的卧室里。虽然这不是前所未有的孤独,但是身在异乡发高烧无人照顾也显得有些凄惨,我想了很多事情。

关于工作

以前可能没有这种感觉,但是现在这种感觉突然越来越浓重,那就是“我为什么要离开360”。

第一家实习公司

在2017年的六月到2018年的四月分别实习了三个公司,分别是百度,360,还有现在的公司。百度作为一个典型的大厂,虽然在人们中的口碑越来越差,但是老话说的好,瘦死的骆驼比马大呀。但是我感觉在百度并没有“比马大”这种感觉,工作了一年之后回想起在百度的将近半年的时间,我感觉我所在的那个部门是真的挫,完全就是在像搬砖,而且是搬不完的砖。只要是谁到了马上就得上手业务写代码,毫无团队建设,技术沉淀之类的。

大多数人对于项目的架构自己也是完全不理解,有时候问带我的mentor他也是一知半解,说这是上层的事情。甚至还有同事和我说:“你不搬砖怎么领导别人搬砖呢,写代码也一样”。我当时还觉得特别有道理。刚开始去的时候抱有对技术的热情,经常问其他人一些东西,别人分享的东西也参与讨论,后来后来发现他们不屑于和我交流。什么问团队中厉害人物甚至对我是敷衍。我当时觉得幸好这就是实习,但是当时就在我心中扎根了一个想法就是:“大公司都是这样,刚进来的时候都要从搬砖工变为包工头的”。但是现在想想那岂不是把很多人的才华磨灭了一段时间么,有能力的人在大公司一开始肯定也是马上发光发热的。就像我这个公司已经离职的leader,在百度待了两三年就升为T7了。

有的人在code review别人代码的时候完全就是为了code review而去code review。举个例子:写一个react组件传递样式一般有两种,通过classname或者style,我当时实现这个组件考虑了这两种情况,我使用的时候觉得传递className要新加文件等好多东西就直接传递了className。当时把我叫过去说以后不要这样搞了,原因就是不要写行内样式。我当时说出了我的想法,说这么简单的东西完全没必要,而且网上成熟的组件都是这样实现的。却得到傲慢的回答:“我现在给别人code review代码的时候,最怕的就是听到‘别人是这么做的’,‘网上都是这样写的’这类的话,因为他们根本就是错的”。我当时心想:“你可能怕的原因是因为你并不能说出‘网上’和‘别人’为什么错吧”。从那之后我再也没有反驳他的任何话了,下一次反驳就是我发现他连ES6的基本语法不知道把想合的代码打回来。

有一些好的地方就是保持了大公司一贯有的对于项目流程的严谨把握。项目评审,开发流程,上线流程都是非常严谨。当线上遇到bug的时候都是很慌的。。。

第二家实习公司

现在想想在360企业安全的时候,那时候才是真的实习。不写过多的业务代码,老大通过让我做不同的东西看我的学习能力,看能否达到要求。

刚去的时候第一天发邮件申请开发机,因为格式不对,被老大批评说脑子进水了。
然后后面因为不喜欢和同事一起去吃饭,遇到问题不喜欢和同时讨论经常收到批评。
还被老大说我不合群,不适合团队。
因为写文档的事情,我说话的时候说了一句“这个本来就不能编辑”,他说我做事要深入思考,要从设计者的原理去想,为什么这样。直到这句话还是很有用。
告诉我的mentor不要管我了,让我自生自灭。
让我看github的API的时候,我总结出来的东西不行时候对我批评数次。
我当时觉得真的是很难接受,但是现在想想真的因为那几个月我才有了很多的成长,做事不毛手毛脚了,做事喜欢和别人商量了。但是这些成长都是当时看不见的,现在感觉到了,但是。。。当时作为一个相当于温室里的大学生来讲,很少收到这么多批评,一时间有点受不了。

其实老大这个人是特别棒的,做这些也都是为了团队为了我着想。因为团队也是新成立的部门将近二十个人的样子,没有太多的业务去做,团队正在处于0到1的阶段。每天加班到最晚的工作最多的也就是那两个leader,其实已经作为他那个level的人物完全不用搞基层的代码,他每天基本中午两点都会有会议,将近一点去吃饭,可以说是工作最饱和的人了。然后还要抽出时间去关心团队代码的review,关注项目的架构。或许我永远不会忘记的一句话就是他利用午休的时间和我说,当时也是我提出要走的时候,他说:“义龙,我知道你很慌,大家现在都很慌,有的人慌是因为不知道做什么,有的人慌这件事做了不知道是为了什么。今天下午你会看到我有三个会去开,去和别人沟通我们这种方案,让别人相信我们这种架构。你还乐意跟着我干么?”。这也不是我第一次提离职了,我还以学校毕业生信息采集为借口的原因回家待了几天,在这期间和老大发微信提离职,然后给我回了电话说了一下以前对我严厉的原因。

当老大告诉HR给我发offer的时候,HR告诉老大我没有接offer,老大就又想找我谈谈。我就直接说了我得回学校了,老大阅历这么多,当然也听出来了。老大就欣然同意了,剩下的什么话也没说。这是最短的一次谈话了,剩下的几天我就是整理遇到的辞职遇相关的东西。

因为我已经决定来现在这个公司了,原因很简单就是因为这个公司给得钱多。我当时想的就是赶快挣老婆本,然后回家种田,也不想成为什么技术大牛什么的。

现在的公司

从360离职之后在家待了几天就来这个公司了,因为360和百度给是实习工资比较低,所以欠下了一些钱。这个公司实习每天给四百块,所以着急还债就又来这个公司实习了。每天的工作量也不算大,但是一心想着回学校快活也没想着那么多,每天就想着赶快干完活然后去学毕业设计相关的东西。带我的mentor对我也是很不错的,也是挺厉害的一个大牛。基本上没有什么吐槽的地方。但是这个公司的部门分配和其他的公司是不一样的,其他公司都是前端是一个部门,后端是一个部门,然后又项目的时候分配人去搞。这个公司的部门的前端后端产品甚至测试一起的。所以前端的leader是个后端工程师,甚至懂的前端不是很多。实习的时候最大的感触就是这里的前端不是很重要。

现在的状况

现在已经毕业入职一个月了,我发现我想的不一样,并不是赶快挣老婆本的钱就够了,我对技术还是又追求的。因为以前实习过的原因,mentor看我主动性挺好,就把我分到另一个组这个组就十个人左右,就我一个前端。我真的感觉到了在技术上的孤独,甚至连个讨论的人都没有了。我当时来决定来这个公司的时候已经想到了同事的前端水平不是很高的问题,但是没有想到竟然会变得就我一个人了。我其实也不能说过多的话,因为毕竟是人家给我钱,我是来打工的。说句不好听的,搬砖难道还想着穿什么衣服么。每天就是做着后端传过来什么,进行显示或者简单的逻辑就能解决的问题。甚至他们讨论什么我都插不上话。现在以前的mentor成为了其他组的leader了,经常开会也变得忙了起来。我的问题我都不到万不得已就不去找他。

为什么离开360

现在开始说为什么离开360,是真的单单纯纯的因为钱。我做这个决定的时候也和当时还联系的晓兵做了一个比喻,说我就好像一个婊子一样,因为钱放弃了自己喜欢的东西。很长一段时间我也在安慰自己,说可能在这个公司也会又很多学到的东西。
当你面试这个公司的时候,我和二面面试官也说为什么离开百度,我想在不算小的创业公司去更早的接触项目的顶层的一些东西。我感觉我说这个真的是笑话,本来在360的时候就能经历项目从零到一的过程,而且是顶级大佬带队,对技术架构的理解更好不过了。在这个公司其实也能接触到项目从零到一的过程,但是那时候我回学校了。
现在想来真的有点对不起老大的,我上班的时候因为老大经常批我,我觉得留不下了,就还出去面试了几个公司。老大还尽力的劝我留下来。
我现在所有的感受我感觉是是我最有应得的,得到一些东西的时候往往会放弃一些东西。

实习学到的东西

其实这个应该早就写的了,但是一直是懒没有写,可能以前写的感受应该也不是很在时机,但是现在能冷静的分析一下了。

百度

在百度学到的就是严谨的项目流程,以及到点必须把活干好。和公司只是劳务关系,只要你在职一天就得看一天的活儿。

360

360学到的就是,要懂得团队协作,你不会的东西可能别人已经搞的很成熟了。遇到一个问题要先直到目标,然后再说实施方法。我现在做事情也懂得先和别人交流了。在公司并不是干活儿去的,是为了看看我是不是适合这个团队,从下面几个方面:

  • 老大整理github的V3的API,让我看看我的执行力。是否和别人交流(可能别人已经梳理过其他类似的了,可以寻求一些想法),以及对于目标的理解(老大一直在问我目标是什么,然后再去落实步骤)
  • 让我利用github的API做一个微信小程序,看看我的学习能力
  • 想让和不同的人做不同的事情,和不同的人去讨论不同的东西,为了让我更好的融入团队。

现在的公司

Typescript配合React实践


文章首发:Typescript配合React实践


使用ts写React代码写了将近三个月,从刚开始觉得特别垃圾到现在觉得没有ts不行的一些实践以及思考。
如果按部就班的写React就体会不到使用ts的乐趣,如果多对代码进行优化,进行重构,在业务中实践比较好的一些方案就会体会到ts真正的乐趣,但是ts也在过程中给我带来了痛苦,在本文的最后会具体展开一下。

使用ts的心态变化

刚开始觉得ts好垃圾,觉得React的PropTypedefaultProps 几乎能做ts的静态类型检查能做到的事情,甚至做的还能比ts做的多。比如说对于组件间设置默认值,ts对于支持的就是不太好。

后来由于一个需求我改变了一点我的想法,当时的想法就是:“你还别说,这个ts还有点用”。这个场景分为两种情况:

  1. 父组件传递子组件的参数名要发生变化,按照以前都是要通过commamd(ctrl) + f的方式去全局搜索并且修改,但是这样还是如果对于量大话就很不友好(我遇到的就是量大的情况),如果统一替换的话,比如说这个变量叫做user,就有很大概率会包含其他的变量,这样统一替换就会很尴尬。但是ts的静态类型检查就帮你解决了这个问题,对于每一个父组件没有传递的值来说,都会提示错误。而且ts的报错是在编译时,不是在运行时
  2. 但是如果传递的参数名不变,参数值变了的话,ts的静态类型也会帮你检查出来,然后开发人员再去做修改。说了这些比较抽象,上个示例代码比较清晰:
// 父组件
render(): ReactNode {
    const { user, loading, namespaceList, yarnList, workspace } = this.state;
    return (
      <UserDetail
        user={user}
        loading={loading}
        namespaceList={namespaceList}
        yarnList={yarnList}
        workspace={workspace}
        onLoadWorkspace={(params: IParams) => this.onLoadWorkspace(params)}
        onUpdateUser={(field: string, data: IUserBaseInfo) => this.handleUpdateUser(field, data)}
        onToCreateAk={(userId: number) => this.handleToCreateAk(userId)}
        onDelete={(ids: number[]) => this.handleDelete(ids)}
        onUpdateResource={(userId: number, data: IResources) => this.onUpdateResource(userId, data)}
        onAkDelete={(ak: IPermanentKey) => this.handleDeleteAk(ak)}
        onChangeAkStatus={(ak: IPermanentKey, status: string) => this.onChangeAkStatus(ak, status)}
      />
    );
  }

只要注意第一个参数就可以了,这个是实际的业务场景,下面是子组件:

export interface IProps {
 user: IUser | null;
 loading: boolean;
 namespaceList: { namespace: string }[];
 yarnList: IYarn[];
 workspace: {
   list: IWorkspace[] | null,
   total: number,
 } | null;
 onLoadWorkspace: (params: IParams) => void;
 onUpdateUser: (field: string, data: IUserBaseInfo) => void;
 onToCreateAk: (userId: number) => void;
 onDelete: (ids: number[]) => void;
 onUpdateResource: (userId: number, data: IResources) => void;
 onAkDelete: (ak: IPermanentKey) => void;
 onChangeAkStatus: (ak: IPermanentKey, status: string) => void;
}

class UserDetail extends Form<IProps, {}> {

看,如果这样写的话,就能覆盖住上面的两种情况了。

当我硬着头皮准备去修改同事上千行的React代码时候,我刚开始犹豫了好长时间,怕赶在上线发版之前搞不完之类的,后来实践的时候发现意淫的有点多了,有了ts不用关心这么多了呀。大致为父组件给子组件传递的值和回调定义好就ok了。这么说可能有点宽泛,好像自己写一个组件也是这样的,哈哈。后面会具体的提到怎么使用ts重构的。这个时候对于ts的心态就是:“这个东西是真的厉害”。

经历了几次重构自己和重构其他人代码的时候,我现在对于ts的心态就是:“我可能以后的前端生涯离不开这玩意儿了”。

项目架构

因为在网上能搜到的ts+react的项目还是比较少,真实的实践也是比较少,都是一些从头开始配置项目的。文件的目录结构怎么做比较好还是没有具体的实践方案。当然,这种方案还是要根据具体的业务来分析的。在上一篇文章编写不用redux的React代码中说明我当前遇到的业务场景。

最终决定把所有的interface都放在公用的schemas目录然后在具体的业务中进行具体引用。
具体的common的目录结构如下(schems目录下面就保存着所有的接口信息):

common
├── component
│   ├── delete-workspace-modal
│   │   ├── delete-workspace-modal.less
│   │   ├── delete-workspace-modal.less.d.ts
│   │   └── index.tsx
│   └── step-complete
│       ├── index.tsx
│       ├── step-complete.less
│       └── step-complete.less.d.ts
├── css
│   └── global.less
├── hoc
│   ├── workspace-detail.tsx
│   └── workspace-list.tsx
├── schemas
│   ├── dialog.ts
│   ├── k8s.ts
│   ├── ldap.ts
│   ├── message.ts
│   ├── params.ts
│   ├── password.ts
│   ├── section.ts
│   ├── table.ts
│   ├── user.ts
│   ├── workspace.ts
│   └── yarn.ts
└── util
    ├── field-value.ts
    ├── format-datetime.ts
    ├── genURL.ts
    ├── getNamespaceList.ts
    ├── getYarnList.ts
    └── validation.ts

在schems目录下面的文件就类似于通用的静态类型,和业务相关但并不是和某个模块进行强绑定,这是因为在每个模块之间难免会遇到一些交叉。下面是某个具体模块的静态类型:

export interface IYarnResource {
  id: number;
  namespace: string;
  user: string;
  queue: string;
}

export interface IYarnStatus {
  name: string;
  error: string;
  maxCapacity: number;
  state: string;
  used: number;
  capacity: number;
}

export interface IYarnEntity extends IYarnResource {
  status: IYarnStatus;
  keytab: string;
}

和模块强耦合的静态类型比如说propsstate的静态类型,都会放在绝体的业务文件中,就比如说下面的这个代码(简化后):

import React, { PureComponent, ReactNode, Fragment } from 'react';
import { IComplex } from 'common/schemas/password';
export interface IProps {
  onClose(): void;
  onOK(data: IComplex): void;
  complex: IComplex | null;
}
export interface IState extends IComplex {
}
class PasswordComplex extends PureComponent<IProps, IState> {
    state: IState = {
    leastLength: 6,
    needCapitalLetter: false,
    needLowercaseLetter: false,
    needNumber: false,
    needSpecialCharacter: false,
  };
}

所有的业务静态类型一般都是不可复用的,一般是通用静态类型以及某些特殊的静态类型组合而成的。

state的初始化不一定要放在constructor里面,但是一定要给state指定类型,具体的原因见:Typescript in React: State will not be placed in the constructor will cause an error

具体静态类型实践

如果我们安装了@types/react,在react目录下的index.d.ts会有react的所有静态类型定义。

具体组件架构

现在比如写一个模块叫用户管理,里面包含查看用户详情查看用户列表新建用户等功能。这也就对应这三个路由/users/:id/users/users/create。这也就对应着三个有状态组件分别为:user-detail-wrapper, user-list-wrapper,user-form-wrappper。有状态组件里面只是请求或者获取数据之类的。展示是通过component下面的无状态组件。可以看看下面的目录结构:

user
├── component
│   ├── password-complex
│   │   ├── index.tsx
│   │   ├── password-complex.less
│   │   └── password-complex.less.d.ts
│   ├── user-detail
│   │   ├── index.tsx
│   │   ├── user-detail.less
│   │   └── user-detail.less.d.ts
│   ├── user-detail-ak
│   │   ├── index.tsx
│   │   ├── user-detail-ak.less
│   │   └── user-detail-ak.less.d.ts
│   ├── user-detail-base-info
│   │   ├── index.tsx
│   │   ├── user-detail-base-info.less
│   │   └── user-detail-base-info.less.d.ts
│   ├── user-detail-resource
│   │   ├── index.tsx
│   │   ├── user-detail-resource.less
│   │   └── user-detail-resource.less.d.ts
│   ├── user-detail-workspace
│   │   └── index.tsx
│   ├── user-form-dialog
│   │   ├── index.tsx
│   │   ├── user-form-dialog.less
│   │   └── user-form-dialog.less.d.ts
│   └── user-list
│       ├── index.tsx
│       ├── user-list.less
│       └── user-list.less.d.ts
├── user-form-wrapper
│   └── index.tsx
├── user-detail-wrapper
│   └── index.tsx
└── user-list-wrapper
    └── index.tsx

有状态组件

设置只读的state

看过网上的好多实践,为了防止state的不可篡改,都会把state通过下面的方式设置为只是可读的,这种方式虽然好,但是在我的项目中不会出现,这种错误只有React接触的新人或者以前写Vue的人会犯的,我的项目中一共两个人,不会出现在这种问题。

const defaultState = {
  name: string;
}

type IState = Readonly<typeof defaultState>

class User extends Component<{}, IState> {
  readonly state: IState = defaultState;
}

但是上面这种方式只是适合类型为typescript的基本类型,但是如果有自己定义的复杂类型,比如说下面这种:

interface IUser {
  name: string;
  id: number:
 age: number;
  ...
}

interface IState {
  list: IUser[];
  total: number;
}
// default state

const userList: IUser = []
const defaultState = {
  list: userList,
  total: 0,
}

上面这种就不能通过一个单纯的空数组就推断出list的类型是IUser的数组类型,所以要添加无谓一个userList定义。

无状态组件

无状态组件也被称为展示组件,如果一个展示组件没有内部的state可以被写为纯函数组件。
如果写的是函数组件,在@types/react中定义了一个类型type SFC<P = {}> = StatelessComponent<P>;。我们写函数组件的时候,能指定我们的组件为SFC或者StatelessComponent。这个里面已经预定义了children等,所以我们每次就不用指定类型children的类型了。
下面是一个无状态组件的例子:

import React, { ReactNode, SFC } from 'react';
import style from './step-complete.less';

export interface IProps  {
  title: string | ReactNode;
  description: string | ReactNode;
}
const StepComplete:SFC<IProps> = ({ title, description, children }) => {
  return (
    <div className={style.complete}>
      <div className={style.completeTitle}>
        {title}
      </div>
      <div className={style.completeSubTitle}>
        {description}
      </div>
      <div>
        {children}
      </div>
    </div>
  );
};
export default StepComplete;

泛型组件

先看一个组件,这个组件就是展示一个列表。

import React, { Fragment, PureComponent } from 'react';

export interface IProps<T> {
  total: number;
  list: IYarn[];
  title: string;
  cols: IColumn[];
}

class YarnList  extends PureComponent<IProps> {

}

当我们想通用这个组件的时候,但是就是列表的字段不一样,也就是列表对应的不同的类型。这个时候我们可是使用泛型,把类型传递进来(也可以说是通过typescript的类型推断来推断出来)。来看下面的具体实现:

export interface IProps<T> {
  total: number;
  list: T[];
  title: string;
  cols: IColumn[];
}

class ResourceList<T> extends PureComponent<IProps<T>> {
  // 我们现在业务的场景会把这个list传递给table,table不同的字段通过外部的父组件传递进来。
tableProps(): ITable<T> {
    const columns: IColumn[] = [
      ...this.props.cols,
      { title: '操作', key: 'operation', render: (record: T) => this.renderOperation(record) },
    ];
    return {
      columns,
      data: this.props.list,
      selectable: false,
      enabaleDefaultOperationCol: false,
      searchEmptyText: '没有搜索到符合条件的资源',
      emptyText: '尚未添加资源',
    };
  }
}

设置默认值

如果使用的typescript是3.x的版本的话,就不用担心这个问题,就直接在jsx中使用defaultProps就可以了。

如果使用的是2.x的版本就要如果定义一个类似下面这样一个可选的值:

interface IProps {
  name?: string;
}

我们如果在class里面设置defaultProps的话,ts是不认识的。还是要在代码里面进行非空判断。对用这好昂方法可以写一个高阶组件。高阶组件来源

export const withDefaultProps = <
  P extends object,
  DP extends Partial<P> = Partial<P>
>(
  defaultProps: DP,
  Cmp: ComponentType<P>,
) => {
  // 提取出必须的属性
  type RequiredProps = Omit<P, keyof DP>;
  // 重新创建我们的属性定义,通过一个相交类型,将所有的原始属性标记成可选的,必选的属性标记成可选的
  type Props = Partial<DP> & Required<RequiredProps>;

  Cmp.defaultProps = defaultProps;

  // 返回重新的定义的属性类型组件,通过将原始组件的类型检查关闭,然后再设置正确的属性类型
  return (Cmp as ComponentType<any>) as ComponentType<Props>;
};

Typescript不好的地方

就类型定义起来有点费劲,有的时候废了大半天的力气发现都是在整ts类型的问题。
然后。。。应该没有了。

前端开发规范

这里就主要介绍在书写组件的时候的个人开发规范:

  • 字段内容要尽量到末尾再去解释。
    • 例: 一个组件要给一个子(子...)传递一个对象参数,但是现在可以想象到的这个组件只用name字段,为了可扩展,不要只是给这个子(子...)只是传递name属性,要把整个对象传递过去。
    • 例:一个无状态组件能修改用户的姓名,当点击确定按钮进行修改的时候,不要只是把修改后的姓名传递回去,要把整个都传递回去。
  • 有状态组件只是处理响应和请求逻辑,不处理任何展示信息。也就是说有状态组件中的render函数中只是给子组件传递信息
  • 无状态组件可以保存一些state的信息,比如说一个弹窗的展示和隐藏。
  • 一个组件不能超过300行代码
  • 两行缩进(不同的编译器使用.editorconfig)
  • 通用的interface放在common下面的schemas下面
  • 非通用的interface比如说IProps或者IState要放在组件的内部
  • 超过两个地方可以用的东西,要抽象

参考

需要去思考的问题(持续更新)

在工程中做了统一的接口错误处理,但是遇到特殊情况怎么办?

因为现在在项目中使用的是axios,通过axiosdisplayNotification会把所有的请求失败的接口统一处理,现在的统一错误处理是只要是错误就出现个弹窗把后端返回的错误显示给前端。但是也是会有特殊情况,在登陆的时候当用户名和密码出现错误的时候,一般在输入框的周围出现红色文字提示,但是如果使用统一的错误处理话还是会出现弹窗,这样对于交互或者体验来说是很不好的,现在的做法:因为这种情况比较特殊,所以就通过判断后端返回回来的错误信息,这个场景就不进行出现弹窗

  • 方案: 无

但是项目中难免会出现一些奇葩或者历史遗留性问题,有的接口默认返回的都是200,那个开发者觉得只要这个请求请求到服务器了就是成功就应该返回200,但是把具体的错误有进一步的包了一下,但是这个请求除了404意外都是200,所有就不会走到上面所说的displayNotification里面,所以这种情况的做法:就要使用axiosinterceptors对每个请求的响应进行特殊的判断

  • 方案: 无

公用模块频繁升级版本

在当前的业务中,不同的业务线是分为不同的repo的,这样做是有很多好处的:

  1. 不同的业务线之间不会相互影响
  2. 不同的业务线采用的架构发生变化,不用一起商量
  3. 把各个业务的公用模块打成一个包,单独维护。(比如说统一鉴权的逻辑)

还是有坏处的:

  1. 在引入公用模块的时候,按照规范来说是不应该出现样式重叠的,但是公用模块如果依赖normalize.css等有全局的东西话就可能会遇到问题。比如现在遇到的问题是:公用模块的样式中有一个* {box-sizing: border-box}但是有的模块是不依赖这个的,就会出现问题:
  • 方案: 规范流程
  1. 共有模块在bugfix的时候导致各个业务模块频繁升级。
  • 方案: 在可控范围内不生版本升级(不采纳,不可控)
  • 方案: 通过引入写一个第三方组件,这个组件能通过远程加载unpkg上指定的组件,这样能够保证每次发版只用公共组件的一个最新或者最稳定版本

底层依赖的component有bug

现在的场景是这样的:

  • rc-component是一套开源的组件库,里面封装了常用的基础组件如:rc-select, rc-tooltip, rc-
  • 有一套基础组件lamma-ui是基于rc-component的;
  • 还有一台业务通用组件apollo-weights是基于lamma-ui的;

并且内部的lamma-uiapollo-weights已经稳定使用了很长时间了,突然有一天发现了rc-component中的某一个组件的bug(比如说: rc-select),并且这个bug是block的会影响产品展示形态。

  • 现在的方案: 自己把apollo-weights中的代码copy一下到本地,然后升级一下里面有bug的组件,升级到最新的没有bug的版本。
  • 想实现的方案(影响较大): 把基于rc-component的组件定期升级到最新的版本,然后使用组件的业务进行测试。
  • 更好的方案: 无

MAC命令行常用快捷键

跳转到行首

  1. Control + a
  2. FN + 👈

跳转到行尾

  1. Control + e
  2. FN + 👉

光标向左移动一个单词

ESC + b

光标向右移动一个单词

ESC + f

删除后面的一个字符

Control + d

删除光标至行尾的所有字符

Control + k

删除光标到行首的所有字符

Control + u

删除当前位置到单词开头的所有字符

Control + w

清屏

Control + i

工作常用Linux命令

lsblk

lsblk -o name,kname,fstype,mountpoint,model,size,rand

lsblk

NAME 物理盘以及分区
KNAME 设备的名称
FSTYPE 文件系统类型
MOUNTPOINT 系统分区挂载点
MODEL 设备的型号信息
SIZE 分区大小
RAND 0代表SSD,1代表非SSD

磁盘分区

mkfs.ext4 /dev/sdb
mount /dev/sdb /mnt/disk1

为什么即将使用react-redux

目前在项目中还没有使用任何数据管理工具,目前的状态都是通过放在父组件的state里面。(前面的两篇文章也介绍了项目的主要结构:编写不用redux的React代码Typescript配合React实践)。

不用数据管理工具的原因

一直不舍得在项目中使用数据管理工具的原因有下面两点:

  • 本以为以这种规模的项目,不用引入一个数据管理工具;
  • 自己对数据管理工具不算熟,也没有什么具体实践,怕耽误项目的进度;

不得不引入数据管理工具

不用数据管理工具能解决问题,但是解决的不优雅

1. 承担路由的组件承载过多代码

我们一般会有一个组件,把一个项目所有的路由都放在里面进行配置,没有过多的逻辑。但是项目要求在展开详情的时候显示面包屑,如下图一下:

image

先看一下路由组件的现在代码:

        <Fragment>
        <Route exact path="/user" component={User} />
        <Route path="/user/:id" render={(props: IProps) =>
          <UserDetailWrapper
            {...props}
            getBCPaths={(bcPaths: IPath[]) => this.getBCPaths(bcPaths)}
          />
        } />
        <Route path="/ldap" render={(props: IRouterProps) =>
          <Ldap
            {...props}
            getBCPaths={(bcPaths: IPath[]) => this.getBCPaths(bcPaths)}
          />} exact />
        <Route path="/ldap/:id" render={(props: IProps) =>
          <LdapDetailWrapper
            {...props}
            getBCPaths={(bcPaths: IPath[]) => this.getBCPaths(bcPaths)}
          />} />
        <Switch>
          <Route path="/k8s-queue" component={K8sQueue} exact />
          <Route path="/k8s-queue/create" component={K8sFormWrapper} />
          <Route path="/k8s-queue/:name" render={(props: IK8sProps) =>
            <K8sDetailWrapper
              {...props}
              getBCPaths={(bcPaths: IPath[]) => this.getBCPaths(bcPaths)}
            />}
          />
        </Switch>
        <Switch>
          <Route path="/yarn" component={YarnList} exact />
          <Route path="/yarn/create" component={YarnFromWrapper} />
          <Route path="/yarn/:id" render={(props: IProps) =>
            <YarnDetailWrapper
              {...props}
              getBCPaths={(bcPaths: IPath[]) => this.getBCPaths(bcPaths)}
            />} />
        </Switch>
        <Route path="/updateLdap/:id" component={LdapForm} />
        <Route path="/createUser" component={CreateUser} />
        <Route path="/monitor" component={ModuleStatusModule} />
        <Route path="/node-list" component={NodeListModule} />
        <Route path="/job-list" component={JobListModule} />
        <Route path="/config" component={ConfigCenter} />
        <Route path="/license" component={LicenseList} />
      </Fragment>

上面只是其中一部分,能明显的看到好多组件中多了个getBCPaths这个方法,这个方法就是在通过父子组件通信的方式得到具体组件的面包屑路径,我们就以上面代码中的UserDetailWrapper这个组件来说,这个组件是个有状态组件,按理说不应该承担这种业务,但是还是不得不在组件的componentDidMount中去回调这个方法来把面包屑的路径传递出去。UserDetailWrapper的代码如下:(本来想都贴上,后来有一百多行就贴个简化版本的把)

import React, { ReactNode, PureComponent } from 'react';
import { RouteComponentProps } from 'react-router';

export interface IProps extends RouteComponentProps<{ id: number }> {
  getBCPaths?: (bcPaths: IPath[]) => void;
}

class UserDetailWrapper extends PureComponent<IProps>{


  componentDidMount() {
    const { getBCPaths } = this.props;
    const bcPaths: IPath[] = [
      {
        name: '用户列表',
        path: '/user',
      },
      {
        name: '详情',
      },
    ];
    getBCPaths && getBCPaths(bcPaths);
  }

  render(): ReactNode {
    const { user, loading, namespaceList, yarnList, workspace, complex } = this.state;
    return (
      <UserDetail
        user={user}
        loading={loading}
        />
    );
  }
}

export default UserDetailWrapper;

如果引入了数据管理工具之后,这些面包屑的地址,都放在配置文件中,然后在在管理路由的中进行简单匹配就可以了,这样就解决了组件和路由之间的强耦合。

2. 跳转路由的时候会多次请求

比如这样的一个场景:在管理员创建用户的时候,可能会给该用户分配了一个资源A,这个资源列表接口和当前的场景无关的。 当我们在查看用户详情的时候,可能会给这个用户修改这个资源,可能把A资源改为B。这个时候在不同的路由页面(创建用户页面和用户详情页面)都会请求一遍获取资源列表这个接口。

可能这样场景会有很多。当然我们也可以通过上面在面包屑的方式,把数据存在最顶层组件(配置路由组件),但是这样带来危害和不可维护性可能比调用多次接口的影响要大。

因为这个场景很有画面感,就不上代码了。

3. 组件层次比较深的时候,中间很多透传

当组件的之间的嵌套层级很深的时候,可能父组件的数据要到子子子组件才能用,中间有过多的透传代码是没必要的。这个也是很有画面感的。

4. 多个地方管理一份数据

比如说管理用户这个操作(不知道叫操作合不合适,也可以说把操作改为界面),可以由多个地方去管理,在A这个地方能进行管理,在B这个地方也能进行管理。暂且不说这种方式好不好,或者说这样的产品设计的好不好,但是就真的是遇到了场景如下:

先解释以下工作区的概念,可以认为是多个用户共同使用同一份东西。

以前我们工作区的入口只有一个,如下图:
image

进去之后是这个样子的:
image
应该是除了难看以外还是很平常的。

现在不一样了,不仅从这个地方能进如工作区管理,还在只有管理员能进的控制台增加了个一模一样的,如下图:
image

这样堪比Apple的设计,随之而来的就带来了以下两个问题:

  1. 路由怎么设计
  2. 像面包屑,创建成功后跳转,创建取消等后退跳转操作以前是写死的,现在如果跳回原来的地方可能就遇到错误。

对待第一个问题,我的方案就是瞎设计。以前叫workspaces现在叫ws-list

对待第二个问题:
只能把以前的路由copy一份改改,放在现在的路由里面咯:
以前的路由:

             <Route key="list-active" path="/workspaces" component={WorkspaceList} exact />
              <Route
                path="/workspaces/create"
                render={(props: IRouterProps) =>
                  <WorkspaceForm
                    {...props}
                    getBCPaths={(bcPaths: IPath[]) => this.getBCPaths(bcPaths)}
                  />
                }
              />
              <Route
                path="/workspaces/:id"
                render={(props: IRouterProps) =>
                  <WorkspaceDetailWrapper
                    {...props}
                    getBCPaths={(bcPaths: IPath[]) => this.getBCPaths(bcPaths)}
                  />
                }
                exact
              />

现在变了这样,只是多了一个参数:

           <Route path="/ws-list" render={(props: IProps) =>
            <WorkspaceList
              {...props}
              baseUrl="ws-list"
            />
          } exact />
          <Route
            path="/ws-list/create"
            render={(props: IRouterProps) =>
              <WorkspaceForm
                {...props}
                baseUrl="ws-list"
                getBCPaths={(bcPaths: IPath[]) => this.getBCPaths(bcPaths)}
              />
            } />
          <Route path="/ws-list/:id" render={(props: IProps) =>
            <WorkspaceDetailWrapper
              {...props}
              baseUrl="ws-list"
              getBCPaths={(bcPaths: IPath[]) => this.getBCPaths(bcPaths)}
            />
          } />

然后在相应的组件里面只用this.props.baseUrl默认值是workspaces
当前也就只能这样解决了,如果使用redux虽然不能解决大问题,但是还是能解决解决在配置路由的组件中减少逻辑代码。

使用哪种数据管理工具

最近也看了一下比较流行的mbox是基于响应式编程的,引入的概念也不像redux那样很多,总的来说是同不错的。但是还是不像做公司第一个吃螃蟹的,还是安安稳稳的使用redux了。

redux方案

这玩意儿要啥方案,直接干就完了

使用redux-action

使用redux-action可以有效的简化actionCreatorsreducers的代码。具体了解请看使用 Redux 打造你的应用

所有redux相关的东西放在一个文件下

为了防止redux的代码平铺使得开发人员变得烦躁,所有都一个文件夹目录下然后通过index导出,如下:


home
├── components
│   ├── list.js
│   ├── recommend.js
│   ├── topic.js
│   └── writer.js
├── containers
│   └── header
│       └── index.jsx
├── index.js
├── store
│   ├── actionCreators.js
│   ├── actionTypes.js
│   ├── index.js
│   └── reducer.js
└── style.js

store目录下面的index.js如下:

import reducer from './reducer'
import * as actionTypes from './actionTypes'
import * as actionCreators from './actionCreators'

export { reducer, actionCreators, actionTypes }

全局状态放在app目录下

redux分为全局状态和局部状态,上一步我们说的是局部状态,如果有比如说loading等全局状态的时候要放在app目录下面使得所有组件都能获取到

浅谈Docker的核心概念和原理(上)

容器的本质是进程

比如我们用c语言写了一个不断接受标准输入然后打印到标准输出的程序。
当这个程序不执行的时候,躺在硬盘就是一个文件,当我们把它运行起来的时候,他就是操作系统里面中的一个进程。

所以对于进程来说,它的静态的表现形式就是硬盘中的一个文件,当它运行起来的时候,就是计算机里面状态和数据的集合,也就说进程的动态表现。

容器也是如此,容器本身也是一个tar包,当我们不运行它的时候,它也是一个文件,当我们把它运行起来的时候,他才是宿主机里面的一个进程。只不错Linux通过一些技术,让这个“进程“有了边界。

也就是我们经常会听到的:Namespace技术改变进程视图,CGroups对进程的资源进行限制

Namespace

一个Docker容器就是宿主机中的一个进程

Docker使用linux内核的namespace技术来实现容器的隔离。

Linux中的Namespace有七个(namespace list)对应的功能如下表:

Namespave 描述 系统调用
UTS 隔离hostname/NIS domain name CLONE_NEWUSER
IPC 隔离进程间通信 CLONE_NEWIPC
PID 隔离PID CLONE_NEWPID
Mount 隔离文件系统 CLONE_NEWNS
Cgroups 隔离cgroups CLONE_NEWCGROUP
User 隔离用户 CLONE_NEWUSER
Network 隔离网络 CLONE_NEWNET

Docker默认开启的Namespace有:

还有一个User Namespace默认不是开启的。

以PID Namespace举例。

当我们理解了,Docker是怎么做到隔离,然后我们来看看官网上展示的图是不是有些问题呢。

image

CGroups

我们知道了Docker是怎么做隔离的,既然一个Docker容器在宿主机就是一个进程(可以通过docker inspect --format '{{ .State.Pid }}' containerId查看容器在宿主机的进程),那么我们就不能让这个进程吃光了我们宿主机的资源,所以就要对使用资源进行限制。

Linux Cgroups 的全称是 Linux Control Group。它最主要的作用就是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等。

Cgroups暴露给用户的操作是文件系统,是通过文件或者目录的方式存在于宿主机操作系统的/sys/fs/cgroup目录下,我们可以通过ll /sys/fs/cgroup将他们展示出来,也可以通过mount命令:

$ mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
......

我们可以看到很多能被限制的资源,比如说cup和memery,他们都是以目录的方式存在的,目录下面的文件是对该资源限制的描述,我们可以看一下cpu下面的文件:

ls /sys/fs/cgroup/cpu
cgroup.clone_children cpu.cfs_period_us cpu.rt_period_us  cpu.shares notify_on_release
cgroup.procs      cpu.cfs_quota_us  cpu.rt_runtime_us cpu.stat  tasks

这里比较重要的是cpu.cfs_period_uscpu.cfs_quota_us,表示在cfs_period这段时间内,该进程最多占用cfs_quota/cfs_period的CPU(-1表示没有限制)。比如我们设置cfs_period=100cfs_quota=50,那么这个进程最多占用宿主机50%的CPU资源。

当我们在/sys/fs/cgroup/cpu这个目录下面新建一个目录containercontainer这个目录就被称为一个控制组,操作系统会给这个子系统生成默认的资源控制文件。

$ sys/fs/cgroup/cpu$ mkdir container
$ /sys/fs/cgroup/cpu$ ls container/
cgroup.clone_children cpu.cfs_period_us cpu.rt_period_us  cpu.shares notify_on_release
cgroup.procs      cpu.cfs_quota_us  cpu.rt_runtime_us cpu.stat  tasks

我们可以先看一下现在操作系统对container的cpu限制:

cat /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us 
-1
$ cat /sys/fs/cgroup/cpu/container/cpu.cfs_period_us 
100000

结论是:没有限制

⚠️
cfs_periodcfs_quota的单位是us,所以上述cfs_period表示的是100000us/100ms。

然后我们执行一个死循环:

while : ; do : ; done &
[1] 7512

image

可以看到机器上的一个cpu已经被打满了。

但是我们的预期是这个死循环进程最多占用一个CPU的百分之二十,那我们应该怎么操作呢?

  1. 已知cfs_period是100000us和进程的占用一个cpu的百分比等于cfs_quota/cfs_period,所以我们需要修改cfs_quota
  2. 修改cfs_quota: echo 20000 > /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us
  3. 把PID加入到containr/tasks里面:echo 7512 > /sys/fs/cgroup/cpu/container/tasks

我们就能看到现在这个进程的CPU资源利用率的上线就是百分之二十了。
image

除了CPU子系统之外,Cgroup对每一个子系统都有其独有的资源限制能力:

  • blkio:为块设备进行i/o限制,用于磁盘等设备
  • cpuset: 为进程分配的单独的cpu和对应的内存节点
  • memery:为进程设定内存使用的限制

我们可以举个在限制docker容器的cpu只能占用20%的例子:

docker run -it --cpu-period=100000 --cpu-quota=20000 centos /bin/bash

查看容器中的:

# cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us
20000
# cat /sys/fs/cgroup/cpu/cpu.cfs_period_us
100000

容器的隔离并完美

众所周知的是容器之间还是共享的宿主机的内核。/proc中存储的是当前内核运行状态的一系列的特殊文件,用户可以通过访问这些文件,查看系统以及当前正在运行的进程信息。比如CPU的使用情况,内存的占用率等,这些文件是查看系统的信息指令(比如top)的主要信息来源。

造成这个问题的原因就是:/proc文件系统并不知道用户通过Cgroup对这个容器做了什么样子的限制

怎么修复呢? lxfs

【翻译】Docker, Containerd & Standalone Runtimes— 这里有你想知道的

原文地址


文章首发


这片文章旨在想您介绍Containerd在docker架构中的集成。

让我们先来定义Docker Daemon然后看它是如何定义进docker架构和Containerd当中的。

Docker Daemon

像进程有守护进程一样(Like the init has its daemon), cron有crond, dhcp有dhcpd, Docker 也有自己的守护进程dockerd。

你能使用下面的命令列出所有的Linux的守护进程:

ps -U0 -o ‘tty,pid,comm’ | grep ^?

译者注:

# 译者使用的是centos 7,原作者的命令不能用,要用下面的
ps -eo tty,pid,comm | grep ^? 

并且在输出中grep出dockerd

ps -U0 -o ‘tty,pid,comm’ | grep ^?|grep -i dockerd 
# 译者注:同上,要用:ps -eo tty,pid,comm | grep ^? |grep -i dockerd 
? 2779 dockerd

你可可能会看到 docker-containerd-shim,我们将要这篇文章的后面去讲解。

如果你已经运行了docker,那么当你在命令后输入dockerd的时候会出现类似下面的信息:

FATA[0000] Error starting daemon: pid file found, ensure docker is not running or delete /var/run/docker.pid

现在让我们来停止docker:

systemctl stop docker

然后在命令行使用dockerd命令运行守护进程。

使用dockerd在命令行运行docker的守护进程是一个很好的debug工具,你能在命令行上看到真实的运行轨迹。

INFO[0000] libcontainerd: new containerd process, pid: 19717 
WARN[0000] containerd: low RLIMIT_NOFILE changing to max  current=1024 max=4096
INFO[0001] [graphdriver] using prior storage driver "aufs" 
INFO[0003] Graph migration to content-addressability took 0.63 seconds 
WARN[0003] Your kernel does not support swap memory limit. 
WARN[0003] mountpoint for pids not found                
INFO[0003] Loading containers: start.                   
INFO[0003] Firewalld running: false                     
INFO[0004] Removing stale sandbox ingress_sbox (ingress-sbox) 
INFO[0004] Default bridge (docker0) is assigned with an IP address 172.17.0.0/16. Daemon option --bip can be used to set a preferred IP address
INFO[0004] Loading containers: done.                    
INFO[0004] Listening for local connections               addr=/var/lib/docker/swarm/control.sock proto=unix
INFO[0004] Listening for connections                     addr=[::]:2377 proto=tcp
INFO[0004] 61c88d41fce85c57 became follower at term 12  
INFO[0004] newRaft 61c88d41fce85c57 [peers: [], term: 12, commit: 290, applied: 0, lastindex: 290, lastterm: 12] 
INFO[0004] 61c88d41fce85c57 is starting a new election at term 12 
INFO[0004] 61c88d41fce85c57 became candidate at term 13 
INFO[0004] 61c88d41fce85c57 received vote from 61c88d41fce85c57 at term 13 
INFO[0004] 61c88d41fce85c57 became leader at term 13    
INFO[0004] raft.node: 61c88d41fce85c57 elected leader 61c88d41fce85c57 at term 13 
INFO[0004] Initializing Libnetwork Agent Listen-Addr=0.0.0.0 Local-addr=192.168.0.47 Adv-addr=192.168.0.47 Remote-addr = 
INFO[0004] Daemon has completed initialization          
INFO[0004] Initializing Libnetwork Agent Listen-Addr=0.0.0.0 Local-addr=192.168.0.47 Adv-addr=192.168.0.47 Remote-addr = 
INFO[0004] Docker daemon                                 commit=7392c3b graphdriver=aufs version=1.12.5
INFO[0004] Gossip cluster hostname eonSpider-3e64aecb2dd5 
INFO[0004] API listen on /var/run/docker.sock           
INFO[0004] No non-localhost DNS nameservers are left in resolv.conf. Using default external servers : [nameserver 8.8.8.8 nameserver 8.8.4.4] 
INFO[0004] IPv6 enabled; Adding default IPv6 external servers : [nameserver 2001:4860:4860::8888 nameserver 2001:4860:4860::8844] 
INFO[0000] Firewalld running: false

现在如果你在另一个窗口运行或者删除一个容器,你就能看到docker的守护进程连接了docker的客户端和容器。

下面是docker的全局架构(docker的C/S架构):
image

Containerd

它的目的是破坏docker架构中的模块化(its purpose is breaking up more modularity to Docker architecture)和与其他行业参(云提供者以及编排服务)与者相比更加中立。

据Solomon Hykes说,截止到2016年的4月,被包含在Docker 1.11中的Containerd已经被部署在数百万的机器中,宣布扩展Containerd的roadmap得到了许多云提供厂商的一件,这些云厂商包括阿里云,AWS,谷歌,IBM,微软和其他容器生态系统的活跃成员。

更多的docker引擎的功能将要被添加到containerd中,以至于containerd 1.0将要提供在linux或者windows上管理容器的所有原语:

  • 容器的执行和监督(supervision)
  • 镜像分发(Image distribution)
  • 网络接口管理
  • 本地存储
  • 原生管道(plumbing)级API
  • 完整的OCI支持,以及扩展的OCI镜像规范

你可能为了构建,传输,运行容器化应用继续使用docker,但是如果你寻找一种标准化组建那么你可以考虑containerd。

Docker Engine 1.11是第一个基于runC(基于OCI技术的runtime)和containerd构建的版本。下图是containerd的集成架构:
image

Open Container Initiative (OCI)组织成立于2015年的6月,旨在建立容器的通用标准为了避免在容器系统内部可能存在的碎片和分裂。

它包含两个标准:

  • runtime-spec: runtime标准
  • image-spec: 镜像标准

runtime规范概述了如何在磁盘上解压文件系统包。

  • 一个标准化容器系统包应该包括所需的配置和信息,为了加载和运行容器,这个配置包含包的在根目录中的config.json里面
  • 一个标准化的容器包应该包括代表容器根文件系统的所有目录,一般这个目录类似rootfs的常规名称。

如果你导出和提出一个镜像你能看到这个json的文件。下面的这个例子,我们将要使用busybox作为例子:

mkdir my_container
cd my_container
mkdir rootfs
docker export $(docker create busybox) | tar -C rootfs -xvf -

现在我们在rootfs中已经有一个提取出来的busybox的镜像。

tree -d my_container/
my_container/
└── rootfs
    ├── bin
    ├── dev
    │   ├── pts
    │   └── shm
    ├── etc
    ├── home
    ├── proc
    ├── root
    ├── sys
    ├── tmp
    ├── usr
    │   └── sbin
    └── var
        ├── spool
        │   └── mail
        └── www

我们能够通过下面的命令生成config.json这个文件:

docker-runc spec

生成的配置文件如下:

{
 "ociVersion": "1.0.0-rc2-dev",
 "platform": {
  "os": "linux",
  "arch": "amd64"
 },
 "process": {
  "terminal": true,
  "consoleSize": {
   "height": 0,
   "width": 0
  },
  "user": {
   "uid": 0,
   "gid": 0
  },
  "args": [
   "sh"
  ],
  "env": [
   "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
   "TERM=xterm"
  ],
  "cwd": "/",
  "capabilities": [
   "CAP_AUDIT_WRITE",
   "CAP_KILL",
   "CAP_NET_BIND_SERVICE"
  ],
  "rlimits": [
   {
    "type": "RLIMIT_NOFILE",
    "hard": 1024,
    "soft": 1024
   }
  ],
  "noNewPrivileges": true
 },
 "root": {
  "path": "rootfs",
  "readonly": true
 },
 "hostname": "runc",
 "mounts": [
  {
   "destination": "/proc",
   "type": "proc",
   "source": "proc"
  },
  {
   "destination": "/dev",
   "type": "tmpfs",
   "source": "tmpfs",
   "options": [
    "nosuid",
    "strictatime",
    "mode=755",
    "size=65536k"
   ]
  },
  {
   "destination": "/dev/pts",
   "type": "devpts",
   "source": "devpts",
   "options": [
    "nosuid",
    "noexec",
    "newinstance",
    "ptmxmode=0666",
    "mode=0620",
    "gid=5"
   ]
  },
  {
   "destination": "/dev/shm",
   "type": "tmpfs",
   "source": "shm",
   "options": [
    "nosuid",
    "noexec",
    "nodev",
    "mode=1777",
    "size=65536k"
   ]
  },
  {
   "destination": "/dev/mqueue",
   "type": "mqueue",
   "source": "mqueue",
   "options": [
    "nosuid",
    "noexec",
    "nodev"
   ]
  },
  {
   "destination": "/sys",
   "type": "sysfs",
   "source": "sysfs",
   "options": [
    "nosuid",
    "noexec",
    "nodev",
    "ro"
   ]
  },
  {
   "destination": "/sys/fs/cgroup",
   "type": "cgroup",
   "source": "cgroup",
   "options": [
    "nosuid",
    "noexec",
    "nodev",
    "relatime",
    "ro"
   ]
  }
 ],
 "hooks": {},
 "linux": {
  "resources": {
   "devices": [
    {
     "allow": false,
     "access": "rwm"
    }
   ]
  },
  "namespaces": [
   {
    "type": "pid"
   },
   {
    "type": "network"
   },
   {
    "type": "ipc"
   },
   {
    "type": "uts"
   },
   {
    "type": "mount"
   }
  ],
  "maskedPaths": [
   "/proc/kcore",
   "/proc/latency_stats",
   "/proc/timer_list",
   "/proc/timer_stats",
   "/proc/sched_debug",
   "/sys/firmware"
  ],
  "readonlyPaths": [
   "/proc/asound",
   "/proc/bus",
   "/proc/fs",
   "/proc/irq",
   "/proc/sys",
   "/proc/sysrq-trigger"
  ]
 }
}

现在你就可以编辑这个配置文件里面的内容,然后不通过docker启动一个容器,通过runc:

runc run container-name

首先在你第一次用它的时候要先安装它:

sudo apt install runc 

你也能通过源文件安装它:

mkdir -p ~/golang/src/github.com/opencontainers/
cd ~/golang/src/github.com/opencontainers/
git clone https://github.com/opencontainers/runc
cd ./runc
make
sudo make install

runC是一个完全符合规范的容器运行时,它允许你扩展(spin)一个容器,能与它们进行交互并且管理它们的声明周期,这要是为什么一个引擎(比如docker)构建的容器,能够在另一个容器中运行的原因。

容器作为runC的一个子进程,也能不通过守护进程(docker的守护进程)切入一些变量到系统之中。

runC构建在libcontainer上,libcontainer也是为docker引擎安装提供相同的容器库。在docker1.11之前,docker引擎用来管理存储,网络,容器以及镜像等等,现在docker的架构被拆为了四个部分:

  • docker engine
  • containerd
  • containerd-shm
  • runC
    二进制文件分别称为:
  • docker
  • docker-containerd
  • docker-containerd-shim
  • and docker-runc

为了运行一个容器,docker engine创建了一镜像,然后把它传递给containerd,然后containerd去调用containerd-shm ,containerd-shm 再去调用runC去运行这个容器。containerd-shim 允许runC在创建容器之后退出。这样我们就可以运行无守护进程的容器,因为我们没必要去为容器运行长时间的runtime进程。

目前,容器通过runC(经由containerd)创建的,但是可能通过另一种非runC的二进制暴露出相同的docker命令行以及接收OCI包。

你能在你的机器上看到不同的runtime通过下面的命令:

docker info|grep -i runtime

如果用的默认的会得到下面的输出:

Runtimes: runc
Default Runtime: runc

通过下面的命令你可以增加一个runtime:

docker daemon --add-runtime "<runtime-name>=<runtime-path>"

# docker daemon --add-runtime "oci=/usr/local/sbin/runc"

按流程它们只是一个containerd-shim,它管理标准的IO的先入先出队列,和保证在容器挂掉的时候能启动容器。

它也负责发送容器退出的状态给像docker这样的上层。

下图是当前的docker的架构。
image

Containerd实现了容器runtime,声明周期的支持和执行(create, start, stop, pause, resume, exec, signal & delete)这些特性。其他的特性(比如存储,日志等)被其他的组件实现。下图是Containerd Github 的一个图,展示了不同的特性并告诉这个特性是是否在范围Containerd之内。
image

我们运行容器:

docker run --name mariadb -e MYSQL_ROOT_PASSWORD=password -v /data/lists:/var/lib/mysql -d mariadb
Unable to find image 'mariadb:latest' locally
latest: Pulling from library/mariadb
75a822cd7888: Pull complete 
b8d5846e536a: Pull complete 
b75e9152a170: Pull complete 
832e6b030496: Pull complete 
034e06b5514d: Pull complete 
374292b6cca5: Pull complete 
d2a2cf5c3400: Pull complete 
f75e0958527b: Pull complete 
1826247c7258: Pull complete 
68b5724d9fdd: Pull complete 
d56c5e7c652e: Pull complete 
b5d709749ac4: Pull complete 
Digest: sha256:0ce9f13b5c5d235397252570acd0286a0a03472a22b7f0384fce09e65c680d13
Status: Downloaded newer image for mariadb:latest
db5218c494190c11a2fcc9627ea1371935d7021e86b5f652221bdac1cf182843

如果你运行ps aux命令,你能注意到docker-containerd-shim和容器运行通过下面的一些参数关联:

  • db5218c494190c11a2fcc9627ea1371935d7021e86b5f652221bdac1cf182843
  • /var/run/docker/libcontainerd/db5218c494190c11a2fcc9627ea1371935d7021e86b5f652221bdac1cf182843
  • runC 程序 ( docker-runc )

这是具有正确格式的完整行:

docker-containerd-shim <container_id>
 /var/run/docker/libcontainerd/<container_id> docker-runc

db5218c494190c11a2fcc9627ea1371935d7021e86b5f652221bdac1cf182843是你创建容器之后能看到的id。

ls -l /var/run/docker/libcontainerd/db5218c494190c11a2fcc9627ea1371935d7021e86b5f652221bdac1cf182843
total 4
-rw-r--r-- 1 root root 3653 Dec 27 22:21 config.json
prwx------ 1 root root    0 Dec 27 22:21 init-stderr
prwx------ 1 root root    0 Dec 27 22:21 init-stdin
prwx------ 1 root root    0 Dec 27 22:21 init-stdout

总结

Docker的每个版本都在变化和发展,但有一些重大变化,比如Containerd的集成。在这篇文章中,我们已经看到了在这次集成(Containerd的集成)之后Docker发生了什么变化,以及OCI(在Docker的帮助下)如何通过引入使用独立运行时的可能性来改变容器世界。

2018年7月第三周学习计划

七月第三周学习计划

详细计划

ES5中Symbol的用法

主要说几个Symbol的使用

消除魔术字符串

先看下面的代码:

function getArea(shape, options) {
	let area = 0
	switch(shape) {
		case 'Triangle':
			area = .5 * options.w * options.h
			break
		/*More case*/
	}
	return area
}

getArea('Triangle', {w: 200, h: 100})

在上面的程序中,我们能看出Triangle就是魔术字符串,我们一般为了消除这种魔术字符串,我们会定义下面的常量:

const shapeType = {
    TRIANGLE: 'Triangle'
}

然后把上面的代码变为:

const shapeType = {
    TRIANGLE: 'Triangle'
}
function getArea(shape, options) {
	let area = 0
	switch(shape) {
		case shapeType.TRIANGLE:
			area = .5 * options.w * options.h
			break
		/*More case*/
	}
	return area
}

getArea(shapeType.TRIANGLE, {w: 200, h: 100})

上面的这种做法的确消除了强耦合。但是我们可以仔细想想,在这种情况之下其实shapeType.TRIANGLE等于哪个值没有影响,所有我们可以把定义的常量变为:

const shapeType = {
    TRIANGLE: Symbol()
}

非私有但只能被内部访问

先看下面的代码:

const size = Symbol('size')

class Collection {
	constructor() {
		this[size] = 0
	}
	add(item) {
		this[this[size]] = item
		this[size]++
	}
	static sizeOf(instance) {
		return instance[size]
	}
}

let x = new Collection()
console.log(Collection.sizeOf(x))

x.add('foo')
x.add('bar')
console.log(Collection.sizeOf(x))

console.log(Object.keys(x)) // ['0', '1']
console.log(Object.getOwnPropertyNames(x)) // ['0', '1']
console.log(Object.getOwnPropertySymbols(x)) // [Symbol(size)]

我们通过keygetOwnPropertyNames都无法获得Symbol的对象,只能通过getOwnPropertySymbols获得。

浅谈Docker的核心概念和原理(下)

制作镜像

如果拿汽车来做比:
Docker好比汽车引擎,
Dockerfile相当于汽车蓝图,
Docker image(镜像)就是汽车样板,
Docker container(容器)类似于汽车的零部件,
Docker Registry可以看作是4s店,
Docker Compose就像老司机,
Docker Volume就像是汽车的油箱, 如果把容器间内的io数据流比喻成汽油,
Docker Swarm(或者K8s)就是交通枢纽。

exec

安装k8s遇到的问题

vim /etc/kubernetes/manifests/kube-apiserver.yaml

  1. worker节点不能curl master节点的apiserver,

在master节点中发现apiserver监听的0端口,在etc/kubernetes/manifests/kube-apiserver.yaml增加了两个字段--insecure-port=service-node-port-range=1-65535重启kubelet:systemctl restart kubelet然后通过命令netstat -pantl | grep 8989发现监听的是127.0.0.0又在etc/kubernetes/manifests/kube-apiserver.yaml增加了--insecure-bind-address字段为10.0.0.0`就能curl通了

  1. 修改ubuntu的hostname
vim /etc/hostname
vim /etc/hosts
  1. 增加阿里云的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
  1. 安装指定版本的kube*
apt-get install -y kubelet=1.12.0-00 kubeadm=1.12.0-00 kubectl=1.12.0-00  kubernetes-cni=0.6.0-00
  1. The connection to the server localhost:8080 was refused - did you specify the right host or port?
export KUBERNETES_MASTER=http://MasterIP:8080
  1. 查看证书信息
openssl x509 -in ca.crt -noout -text

github-restful-api读后感

github api做的比较好的地方

看完了几篇RESTFUL API的教程文档之后,发现github restful V3实践的是相当到位的,把感受到的优势说一下:

  1. 文档写的好,对资源的从属分类做的特别好,让人一眼就知道大致在做什么。
  2. 对各种接口的变迁有对象的描述,也有对应的解决方案,
    • add-team-memberadd-or-update-team-membership都是在teams中,增加一个成员,但是membership就像是对member的一个升级版本,前者当在teams中邀请成员的时候,如果该用户不存在在orgs中,就会返回相应的状态码,相当于报错。但是后者就做到了平滑的升级,当用户不存在在orgs中的时候就发一封邮件给这个用户,然后把这个用户的状态设置为penging,当用户同意加入teams的时候就把该用户的状态设置为actived
  3. 如果调用不同版本的接口, 或者使用其他的数据格式(不是json)的接口可以通过httpAccept头部字段进行制定。
  4. 更加安全:如果外部访问的内部不开放的资源,不会返回401,会返回404,这是为了不让攻击者轻易找到内部的资源
  5. 使用缓存, 以event这个资源为例子,如果在缓存时间内没有新的event被触发,那么服务端会返回的是304
  6. 对发起请求的角色的请求进行限流操作,减少服务器压力,【rate-limit】【rate-limiting】
  7. 合理的使用状态码

各个资源之间的关系

梳理了github主要的资源以及对资源的操作。

总览图

上一张总览图:
bnxu3 6b 56c 1 xq 9jtv

上图主要花的是资源之间的"从属"关系图,可能您会疑问为什么要用虚线,因为这里的从属是一种“可以”的关系,不是一种“必须“的关系,比如果user -> orgs,要获取orgs不一定是要通过user,也可以通过其他方式,比如get-an-organization

资源内的关系图

user

从总览图中我们知道,通过user我们可以得到这个usergistsorgsrepos等信息,下图展示了以user为中心怎么对应资源进行操作。
j ew0 xt bq 04aetj7 6 v

issues

okr9ejcl8oih_ v5 dlh d

repos

tevr 2 c15mt 90 1p k

teams

m 4auzbic3t8ca uuyr1tg

orgs

3 w f66eix0 3s s6g n5r

剩余的问题

  1. 什么场景用add什么场景用create
  2. 什么场景用remove什么场景用delete
  3. 什么场景用edit什么场景用update

参考文章:

【翻译】52个思考之三: 不同因素的计算和存储能力

不同因素的计算和存储能力

这是这个系列博文的第三篇,为的是解决“每个博士生都应该知道的52件事情”这个系列。这个问题的集合已经被写进博士候选人在第一年结束应该知道的事情里面。我们将在明年呈现每个问题的答案,每周一个,我是被分配到第三个问题的学生。

评估相对的计算和存储能力

  • 一张智能卡
  • 一个微控制器(例如:传感器节点)
  • 一个嵌入式或微型处理器(例如:移动手机或者PDA)
  • 一个笔记本电脑或者台式电脑

为了测量设备的计算能力,我们能估计处理器的时钟速度。如果处理器能够实现某种形式的并行性那么可能会被误解--两核以2GHz运行明显比单核以2GHz运行更具有计算能力,所以找到一个直接的量化指标是不实际的希望。对于像通用图卡等特殊设备,通常会报告设备设备能够维持的总FLOPS(每秒浮点运算)(要么是单精度要么是双精度),但这个措施运用到任何问题时都不是可靠的选择。确实,某些服务通过在各种问题实例上通过对不同的设备进行性能基准测试来便于比较--例如,CompuBench(一个性能测试工具)。幸运的是,问题中包含的设备的功能范围足以减少对量化指标的依赖。

可以更加轻松的找到每个设备的存储功能的度量:我们可以简单的比较设备能够在永久存储上的大致信息字节数。

智能卡具有最少计算量的设备:时钟速度会因实现的不同而变得不同,但是人们可能看到达到20MHz核心速度。在存储方面,典型的智能卡可能只有两千字节。

微型控制器是“包含处理器内核,内存和可编程输入/输出外设的单个集成电路上的小型计算机[1]”存储能力和可计算能力会根据微型处理器的确切定义而变得不同,但是以建议的传感器节点为例子,一个典型的微型控制器可能和智能卡有类似的计算能力和稍微多的存储可用,可能是几千字节到几兆的量级。

像手机这种移动计算机有更多存储可计算能力,可用的电量随时间的推移而迅速增加。以苹果的2008版本和2013版的nexus5来说,苹果使用的是412MHz 32位的ARM芯片,nexus5的CPU使用的是2.3GHz的四核处理器。在存储方面,如果我们忽略一些手机使用移动硬盘,在2013年的高端手机可能提供16到32千兆字节的存储空间。

最后,大多数笔记本电脑和台式电脑拥有比手机更强的处理能力:高端的intel“Haswell” i7 4960K处理器包含四个核每个核有4GHz的时钟,AMD "Piledriver" FX-9590 CPU 包含八个核每个核有4.7GHz的处理能力---注意两个处理器之间直接比较不仅仅评估核数量和时钟速度。还有其他的因素影响台式或者笔记本计算机的计算能力--特别的,对于某些问题,添加图形处理单元可以得到性能上的大幅度提升。台式或者笔记本计算机的容量变化很大,但是消费者机器上的典型存储量在数百个千兆和几兆兆--目前最大的单硬盘容量约为8兆兆。

[1] https://en.wikipedia.org/wiki/Microcontroller

浅谈POD基础

定义共享pod的namespace

Lifecycle

Projected Volume 和SA

健康检查

POD的恢复过程,永远是发生在当前节点上的。事实上,一旦一个POD和一个node绑定,除非绑定发生了变化(修改pod.spec.node),否则就算这台机器挂了,也不会调度到其他的机器上。
而如果你想让 Pod 出现在其他的可用节点上,就必须使用 Deployment 这样的“控制器”来管理
Pod,哪怕你只需要一个 Pod 副本。

podset

Kubernetes中控制器模式

控制循环

kubernetes/pkg/controller/ 下面的都是控制器,这个下面的都是遵循 kube-controller-manager的。

他们都遵循统一的编排模式,即:控制循环,下面的伪代码来解释一下:

for {
    实际状态 := 获取集群中对象 X 的实际状态Actual State期望状态 := 获取集群中对象 X 的期望状态Desired Stateif 实际状态 == 期望状态{
        什么都不做
    } else {
        执行编排动作将实际状态调整为期望状态
    }
}

实际状态的来源可能是:kubelet通过心跳得到容器或节点的状态监控系统保存的应用监控数据控制器主动收集

Deployment控制器模型的实现举个例子:

  1. Deployment 控制器从 Etcd 中获取到所有携带了“app: nginx”标签的 Pod,然后统计它们
    的数量,这就是实际状态;
  2. Deployment 对象的 Replicas 字段的值就是期望状态;
  3. Deployment 控制器将两个状态做比较,然后根据比较结果,确定是创建 Pod,还是删除已有
    的 Pod

image

水平伸缩和滚动升级

Deployment和replicaset的关系
image

【翻译】图解OSI模型(The Layers of the OSI Model Illustrated)


文章首发


原文地址

image

开发式系统通信互联(OSI)模型

开发式系统通信互联(OSI)模型定义了一种实现了在层之间实现网络协议,并且控制一层传递到下一层的网络架构。当今它最主要的用途就是作为教学工具。它在概念上将计算机网络划分为七个不同的层次。低层处理电信号、二进制帧以及通过网络路由这些数据。高层涉及到网络的请求和相应、数据表示以及从用户角度网络协议的展示。

物理层

image

第一层,在OSI模型中的物理层是控制数字数据位(bits)从发送设备(源)的物理层通过网络通讯媒介到达接收(目的)设备物理层。物理层的技术包括:以太网电缆( Ethernet cables)令牌环网络(Token Ring networks)。除此之外,集线器(hub)其他中继器(repeaters)以及电缆连接器也是工作在网络层的标准网络设备。

在物理层,使用物理介质(电压、无线电频率、红外线或者普通脉冲波)支持的信号类型传输数据。

数据链路层

image

当从物理层获得到数据的时候,数据链路层检查物理传输错误,然后将位打包位数据帧。数据链路层也管理物理寻址方案,例如以太网的MAC地址,控制不同的网络设备对物理媒介的访问。以为数据链路层是OSI模型中最复杂的层,它也被拆为“媒体访问控制”和“逻辑连接控制”两个子层。

网络层

image

网络层在数据链路层之上增加了路由的概念。当数据到达网络层,包含在帧内的源地址和目的地址将要被检查,为了确定数据是否到达了目的地。如果确认后到达的是目的地,那么网络层将数据格式化为能够传递到传输层数数据包。否则,网络层将要更新目的地址并且将这个数据帧退回下层。

为了支撑路由,网络层保存了设备在网路中的逻辑地址,例如ip Address。网络层也管理逻辑地址和物理地址的映射。在IP 网络中,这个映射是通过Address Resolution Protocol (ARP)完成的。

传输层

image

传输层通过连接的网络传输数据。TCP是传输第四层网络协议network protocol最常见的例子。不同的传输协议可能支持一系列可选的功能,比如:错误回复,流量控制以及重传的支持。

会话层

image

会话层管理启动和拆除网络连接事件的顺序和流程。在第五层,它支持多种类型的连接,这些连接能够被动态的创建和运行在各个网络中

表示层

image

表示层是OSI模型中功能最简单的一层。它处理消息数据的语法格式,例如格式转换以及支持应用层的加密和解密操作。

应用层

image

应用层提供网络服务给端用户的应用。网络服务通常是用户数据一起使用的协议。例如web浏览器应用,应用层协议HTTP打包发送和接受页面所需要的数据。第七层想表示层提供数据(并从中获取数据)。

【翻译】52个思考之二: 多核处理器和和矢量处理器的不同

多核处理器和和矢量处理器的不同?

从表面上看,你可能对于这两个处理器之间的区别干到很困惑。毕竟你可能更加熟悉并行计算等词,然后会接触到这两种不同的处理器。那么他们之间有什么区别呢?这是‘每个博士生都应该知道的52个问题’这周的问题。在弄清楚它们之间的细节之前,为什么我们不先看看这两种不同处理器之间的概念呢,即并行计算。

什么是并行计算

在回答这个问题之前,我们首先需要考虑传统的“串型”处理模型。让我们想象我们需要解决的一些问题。传统计算解决此问题的方法是将其视为处理器按照顺序处理的多个步骤。处理器处理每一个指令,知道最后得到答案和这个问题被解决。虽然这是解决问题的一个很好的方式,这却意味着在解决问题的速度上遇到的瓶颈。即,处理器执行各个指令的速度。如果问题不是那么大这是一个好方法,但是当我们必须要解决大问题或者要让计算变得更快会发生什么?有没有办法在没有处理器速度的瓶颈的情况下提高计算速度?

你可能已经猜到答案是肯定的,它以被成为并行计算的形式出现。并行计算对我们试图解决的问题所做的是将其分解为更小的问题,每个问题可以在相同时间独立计算。以这种方式,这个问题分布在不同的处理元件上,这些不同的处理元件执行不同的子问题,这样在计算速度上会有很大的潜力。加速量取决于算法并且可以根据Amdahl定律得到[1]。那么这一切是如何运作的呢?怎么能以这种方式处理事情呢?比较好的两个解决方式是多核处理器和矢量处理器。

什么是多核处理器?

多核处理器是单个计算组件,它通过使用多个串行处理器在相同的时间执行不同的操作完成并行计算。前面讨论的大问题的子问题由独立的处理器解决,允许程序并行计算。这就好像有多个人在同一个项目上工作,每个人有不同的任务去做,但是他们都是在为同一个项目做贡献。这可能需要额外的组织去做,但是项目的整体完成速度会变得更快。

什么是矢量处理器

一个矢量处理器是能计算单一指令的处理器(像串型处理器),但是能完成一维阵列排列的多个数据集(不像只能操作单一数据集的标准串行处理器)。这里的想法是,如果你对程序中的不同的数据集做了很多次同样的事情,而不是为每个数据块做一条指令,为什么不对所有数据集执行一次指令呢?SIMD(单指令多数据)通常用于表示这种工作方式的指令。

它们之间的不同是什么

所以这是一个一般的想法,让我们总结一个例子。让我们想象我们想要在路上推四块大石头,并且每分钟只能推一个。串行处理器每次只能推一个所以需要花费四分钟。有两个核的多核处理器相当于有两个人去推石石头所以每次能推两个石头,它需要花费两分钟。矢量处理器就相当于有个长木板,把它放在所有四块石头的后面然后一起去推它们,需要花费一分钟。多核处理器相当于有多个workers,矢量处理器有一种方法可以同时对多个事物在相同的时间内做同一个事情。

引用

[1] http://en.wikipedia.org/wiki/Amdahl%27s_law

浅谈Typescript中的泛型和类型推断

泛型

1. 泛型的目的和介绍

泛型的目的有两个:提供有意义的约束加强类型安全

我们现在这一小结谈一谈提供有意义的约束,第二个问题我们在最后在说。

先上一段js的代码:

const arr1 = []
arr1.push(true)
arr.push(3)

for (let item of arr1) {
  console.log(item)
  console.log(item.toFixed(2))
}// Uncaught TypeError: item.toFixed is not a function

已经为boolean是没有toFixed方法的,所以就会报错的。

当我们使用TS指定类型的时候,代码会是下面这个样子:

const arr1: number[] = []
arr1.push(true) // Argument of type 'true' is not assignable to parameter of type 'number'.
arr.push(3)

这样在编译阶段就会报错,并不会等到运行时。

再来一段代码:让我们手动实现一下数组的reverse方法(这个方法在数组的原型上有):

function reverseArr(arr: number[]): number[] {

  const ret: number[] = [];
  for (let i = arr.length - 1; i >= 0; i--)
    ret.push(arr[i])

  return ret
}

const arr = reverseArr([1, 4, 6])

arr[0] = true // Type 'true' is not assignable to type 'number'.

同刚才说的,数组中的每个元素也都是只能是number类型的。那如果有一天我们想要字符串数组的反转,还得把number改为string?(这里得场景也可以用重载实现,因为这里主要介绍泛型,就不多说了),使用泛型得方式如下:

function reverseArr<T>(arr: T[]): T[] {

  const ret: T[] = [];
  for (let i = arr.length - 1; i >= 0; i--)
    ret.push(arr[i])

  return ret
}

const arr1: number[] = reverseArr([1, 4, 6])
const arr2 = reverseArr<string>(['1', '4', '6']) // arr2: string[]
const arr3 = reverseArr(['1', '4', '6']) //   arr3: string[]
const arr4 = reverseArr([1, '4', '6']) //  arr4: (string | number)[]
arr3.push(4) // Argument of type '4' is not assignable to parameter of type 'string'.
arr4.push(1)
arr4.push('d')

这就体现出泛型的好处了,能指定类型。
arr1没有指定类型,TS是能推断(关于类型推断的东西,在后面会说)出来的;
arr2是通过<string>指定了类型;
arr3arr1也是通过推断出来的;
arr4因为传递的参数numberstring都有,所以就推断为string | number)

2. 泛型interface和泛型class

在上一小节我们介绍的时候简单介绍了泛型变量(声明function reverseArr<T>(arr: T[]): T[] 的时候)。
那如果我们要把这个函数赋值给一个变量的时候,这个变量怎么指定类型呢?请看下面:

const tmp: <T>(parms: T[]) => T[]  = reverseArr;

这样可能略显不清晰,当然也可以把<T>(parms: T[]) => T[]这部分声明为alias。我们也可以通过interface去声明,如下:

interface IReverse {
  <T>(parms: T[]): T[]
}

const tmp: IReverse  = reverseArr;

当然我们还可以提前把reverseArr需要的类型传递了,如下:

interface IReverse<T> {
  (parms: T[]): T[]
}

const tmp: IReverse<string>  = reverseArr;

上面的就属于泛型接口了。

对于泛型类也是同样的道理,直接如下:

class Animals<T, U> {

  name: T;
  getMouseAndLegTotal: (m: U, l: U) => U;
}

const ani = new Animals<string, number>();  // 也可以把这里的number改为string,在实现的对应修改即可

ani.name = 'Dog'
ani.getMouseAndLegTotal = function (m: number, l: number) {
  return m + l
}

我们还能给泛系赋默认值,如下:

class Animall<T> {
  name: T;

}

interface IProps<T> {
  age: T
}


class Dogg<T = number> extends Animall<IProps<T>> {
  sex: T;
}

const d = new Dogg;

d.sex = 'sd'// Type '"sd"' is not assignable to type 'number'.
d.sex = 32

3. 泛型约束

虽然泛型解决了我们代码复用以及对类型的约束,但是我们有时候会觉得泛型有点设计过度了呢,比如我们下面这个函数:

function gao<T>(param: T): T {
  console.log(param.name); // Property 'name' does not exist on type 'T'
  return param
}
gao({name: 'helios'})
gao({name: 'helios2', age: 23})

我们已经保证了,每次传入的参数肯定含有name这个属性,但是我们在gao这个函数中缺不能用。所以我们能进一步的进行约束,先看代码:

interface IHaveName {
  name: string
}

function gao<T extends IHaveName>(param: T): T {
  param.name = 'helios'
  return param
}

gao({name: 'helios'})
gao({name: 'helios2', age: 23})
gao({ age: 23 }) 
//Argument of type '{ age: number; }' is not assignable to parameter of type 'IHaveName'.
// Object literal may only specify known properties, and 'age' does not exist in type 'IHaveName'.

<T extends IHaveName>这个的含义是: IHaveName必须是传入的类型的子集,也就是说T中必须包含 IHaveName中的所有类型,如果不包含就会报上面代码块中gao({ age: 23 }) 的错误。

在泛型中还能使用类型,这里官网上的例子就很好了,我就直接粘过来了:

class BeeKeeper {
    hasMask: boolean;
}

class ZooKeeper {
    nametag: string;
}

class Animal {
    numLegs: number;
}

class Bee extends Animal {
    keeper: BeeKeeper;
}

class Lion extends Animal {
    keeper: ZooKeeper;
}

function createInstance<A extends Animal>(c: new () => A): A {
    return new c();
}

createInstance(Lion).keeper.nametag;  // typechecks!
createInstance(Bee).keeper.hasMask;   // typechecks!

类型推断

Typescript能够根据自己规则去推断(没有声明类型的)变量的类型。

1. 类型推断的使用

基本的类型推断是很容易理解的:
比如:

const fa = 8; // number
const str = ''// string

function add(a: number, b: number) {
  // function add(a: number, b: number): number
  return a + b
}

const adder: (a: number, b: number) => number = function (a, b) {
  // function (a, b) a,b就不用写类型了
  return a + b
}

以上都是符合正常的逻辑,这里还有提一下在tsconfig中的noImplicitAny 参数,如果这个参数不指定的话(默认值为false)如果遇到推断不出来的类型会设置为any;如果指定这个参数的话,如果ts推断不出来会报错,如下代码:

function add2(a, b) { // 压根推断不出来
  return a + b
}

2. 介绍一下conditional type(有条件类型)

条件表达式和编程语言中三目运算符很像,形如:T extends U ? X : Y
T extends U 表示如果U是T的子集那么返回类型X如果不是返回类型Y。
看下面几个例子:

interface IName {
  name: string
}

type typeName1<T> = T extends string ? string : object; 
type typeName2<T> = T extends IName ? IName : object; 

type typeNameA = typeName1<string> // string
type typeNameB = typeName1<number>// object
type typeNameC = typeName2<number>// object
type typeNameD = typeName2<{name: '32', age: 3}> // IName

条件类型的还能处理联合类型,有条件类型会分别处理联合类型中的每个类型,如下:

type typeName1<T> = T extends string ? string : object; 
type typeNameE = typeName1<string | boolean> //  string | object

3. 谈一谈infer

我们有时候想知道一个函数定义(一个实际函数)的参数类型或者返回值,如下:

// 函数
function getP(param1: string, param2: boolean) {
  return true
}
// 函数定义(别名)
type Func = (param: IParams) => boolean

如果我们想知道他们函数参数的类型怎么办呢?现在infer就可以登场了。

infer表示一个在conditional type**待推断的类型(可以是extends后面,也可以在条件类型为true的分支中),可能语言比较枯燥,直接上代码吧:

type GetReturnParam<T> = T extends (...param: infer U) => any ? U : never

type ret1 = GetReturnParam<Func> // [IParams]
type ret2 = GetReturnParam<typeof getP> //  [string, boolean]

infer U就代表去推断参数param的类型。如果是在true去返回这个类型。

那如果我们想要得到函数(函数定义的返回值)怎么办呢,上代码:

type GetReturnType<T> = T extends (...param: any) => infer U ? U : never

type ret3 = GetReturnType<Func> // boolean
type ret4 = GetReturnType<typeof getP> //  boolean

只要把infer的位置换一下就可以了。
反正记住了infer U放在哪就代表去推断哪里的类型

在ts2.8中已经把*ReturnType<T>获取函数返回值类型。*集成进来了。

4[选看]. contravariant(抗变) 和 covariant(协变)

这也不是Typescript提出的新概念了,在其他的高级语言如java/scala中都有类似的概念。在What are covariance and contravariance?(中文版: 协变与逆变)说的挺好的了。

抗变和协变的主要目的是为了保证类型的安全,比如有下面几个类:

class Animal {

}

class Dog extends Animal {

}

class HuaDog extends Dog {
  
}

然后我们定义一个函数类型:

浅谈ES6中迭代器和生成器


文章首发


本文已经默认你已经知道generator是什么以及for...of和数据类型map怎么用了。

前ES6的时代怎么遍历

先来一道思考题:

通过下面的变量

  1. 寻找xiaohong(假设名称唯一)是否喜欢basketball
  2. 所有同学的名字
const students = {
    xiaohong: {
        age: '22',
        fav: ['sleep', 'basketball'],
        teachers: {
            english: 'daming',
            chinense: 'helios',
            math: ['helios2', 'helios3']
        }
    },
    xiaoming: {
        age: '22',
        fav: ['sleep', 'basketball', 'football'],
        teachers: {
            english: 'daming',
            chinense: 'helios',
            math: ['helios2', 'helios3']
        }
    },
}

对于第一个问题来说,我们可以使用各种循环语句:
for/while

for (let i =0 ;i < students[xiaoming].fav.length; i++) {
  if (students[xiaoming].fav[i] === 'basketball') console.log(true)
}


let i = 0;
while(i++ < students[xiaoming].fav.length) {
  if (students[xiaoming].fav[i] === 'basketball') console.log(true)
}

for...of

for (let item of students[xiaoming].fav ) {
  if (item === 'basketball') console.log(true)
}

那么对于第二个问题来说,因为for/while是不能遍历对象的,所以行不通,但是对象有一个专属的遍历方法
for...in

我们来看一下怎么通过for...in来遍历:

for (let stu in students) {
   console.log(stu)
}

你可能会想了,通过for...in去遍历数组会怎样呢?
我们看一下通过for...in去遍历:

for (let item in students[xiaoming].fav) {
   console.log(item)
  // 或者去判断
}

哎呀,通过for...in不也照样能实现数组的遍历么,那为什么不归结到数组的遍历里面去呢!
这里面还有一些细节需要去了解(这也是上面的“对象有一个专属的遍历方法”为什么加粗),我们通过一段代码去解释:

const num = [5, 6, 7]
for (let i in  num) {console.log(i + 1)}

// 01
// 11
// 21

这是因为for-in 是为普通对象({key: value})设计的,所以只能遍历到字符串类型的键。

还有下面这个虽然不常用,但是也是不得不说的:

const arr = [5, 6, 7]
arr.foo = function() {}
for (let i in arr) {
    console.log(i)
}

// 0
// 1
// 2
// foo !!!

foo属于arr上面的方法,被遍历出来是说的过去的。

那么用for...of我们来看看会怎么样

for (let stu of students){}
// Uncaught TypeError: students is not iterable

is not iterable,这个iterable是神马东西,我们接下来下面一步步的看。

先从可迭代(iterable)和迭代器(iterator)说起

iterable是ES6对iteration(迭代/遍历)引入的接口。

如果一个对象被视为iterable(可迭代)那么它一定有一个Symbol.iterator属性,这个属性返回一个iterator(迭代器)方法,这个方法返回一个规定的对象(这个后面会说)。也就是说iterableiterator的工厂,iterable能够创建iteratoriterator是用于遍历数据结构中元素的指针。

两者之间的关系

Axel Rauschmaye大神的图简直不能再清晰了。
image

数据消费者: javascript本身提供的消费语句结构,例如for...of循环和spread operator (...)
数据源: 数据消费者能够通过不同的源(Array,string)得到供数据消费者消费的值;

数据消费者支持所有的数据源这是不可以行的,因为还可能增加新的数据消费者数据源。因此ES6引入了Iterable接口数据源去实现,数据消费者去使用

可迭代协议(iterable protocol)和迭代器协议(iterator protocol)

可迭代协议(iterable protocol)

可迭代协议(iterable protocol) 是允许js对象能够自定义自己的迭代行为。

简单的说只要对象有Symbol.iterator这个属性就是可迭代的,我们可以通过重写(一些对象实现了iterable,比如Array,string)/添加(对于没有实现iterable的对象比如object,可以添加这个属性,使之变为可迭代的)该熟悉使之变为可迭代的。

当一个对象需要被迭代(for...of 或者 spread operator )的时候,他的Symbol.iterator函数被调用并且无参数,然后返回一个迭代器。

迭代器协议(iterator protocol)

迭代器协议定义了一种标准的方式来产生一个有限或无限序列的值。

当一个对象被认为是一个迭代器的时候,它会至少实现next()方法,next()方法返回两个属性value(d迭代器返回的值)和done(迭代时候已经结束)。

还有几个可选的方法可以被实现,具体请看:sec-iterator-interface

iterable协议iterator协议还有next之间的关系

image

来源于网络

然后谈谈ES6中的for...of说起

再文章的最开始我们已经说了再前ES6的时候,如何去遍历。
现在我们说说ES6新增的for...of的作用。

for...in

在前面也已经说了,在ES6之前遍历object的时候用for...in循环,for...in会遍历对象上所有可枚举的值(包括原型(prototype)上的属性),比如下面这样:

function foo() {
    this.name = 'helios'
}

foo.prototype.sayName = function() {
    return this.name;
}
var o = new foo();
for (var i in o) {
    console.log(i)
}
// name
// sayName

如果我们只想遍历对象自身的属性,可以使用hasOwnProperty,如下:


function foo() {
    this.name = 'helios'
}

foo.prototype.sayName = function() {
    return this.name;
}
var o = new foo();
for (var i in o) {
    if (!o.hasOwnProperty(i)) continue;
    console.log(i)
}

如果我们不想让一个对象的属性在for...in中被遍历出来,可是使用Object.defineProperty来定义对象上的属性是否可别枚举(更多的属性请看:Object.defineProperty()),具体如下面代码:

var obj = {name: 'helios'}

Object.defineProperty(obj, 'age', {
    enumerable: false
})

for (var i in obj) {
    console.log(i)
}

在这一小节的最后我们来说说for...in中的in操作符的含义:

prop in obj

  • 含义: 判断prop是否在obj中
  • prop:对象的key属性的类型(string / Symbol)
  • 返回值: boolean

我们来看一组例子:

var o = {
    name: 'heliso'
}
console.log('name' in o) // true
console.log(Symbol.iterator in o) // false
console.log('toString' in o)  // true

这个操作符虽然也适用于数组,但是尽量还是不要用在数组中,因为会比较奇怪,如下代码:

var arr = [6, 7,8]

console.log(7 in arr)  // false
console.log(1 in arr)  // true
console.log('length' in arr)  // true

主要是前两个比较奇怪对不对,因为对于数组prop代表的是数组的索引而为其存在的值。
按照这样的思路,正在看的读者你能思考一下in操作符在字符串中是怎么的模式么?

for...of能遍历的集合

只要是实现了Interable接口的数据类型都能被遍历。

javascript内部实现的有:

  • Array
  • String
  • Map
  • Set
  • arguments
  • DOM data structures

并不是所有的iterable内容都来源于数据结构,也能通过在运行中计算出来,例如所有ES6的主要数据结构有三个方法能够返回iterable对象。

  • entries() 返回一个可遍历的entries
  • keys() 返回一个可遍历的 entries 的keys。
  • values() 返回一个可遍历的 entries 的values。

如果for...of不能遍历怎么办

那就数据结构(数据源)去实现iterable就可以了。

用通俗的话说就是,你如果要遍历一个对象的话,有一下几个步骤:

  1. 对象如果没实现Symbol.iterator那就去实现
  2. 对象的Symbol.iterator函数要返回一个iterator
  3. iterator返回一个对象,对象中至少要包含一个next方法来获取
  4. next方法返回两个值valuedone

现在说说怎么使object变为可迭代的

上面我们已经铺垫了这么多了,我们说了javascript中object是不能被迭代了,因为没有实现iterable,现在让我们来实践一下让object变的可迭代。

第一步: 先尝试着使用for...of遍历object

下面这样写肯定是不行的

const obj = {
    name: 'helios',
    age: 23
}

for (let it of obj) {
    console.log(it)
}
// TypeError: obj is not iterable

第二步: 让object实现iterable接口

const obj = {
    name: 'helios',
    age: 23,
    [Symbol.iterator]: function() {
        let age = 23;
        const iterator = {
            next() {
                return {
                    value: age,
                    done: age++ > 24
                }
            }
        }
        return iterator
    }
}

如果iterableiterator是一个对象的话,上面的代码可以简化为:

function iterOver() {
    let age = 23;
    const iterable = {
        [Symbol.iterator]() {return this},
        next() {
            return {
                value: age,
                done: age++ > 24
            }
        }
    }

    return iterable
}

for (let i of iterOver()) {
    console.log(i)
}

现在生成器(generator)可以出场了

我们如果每次想把一个不能迭代的对象变为可迭代的对象,在实现Symbol.iterator的时候,每次都要写返回一个对象,对象里面有对应的next方法,next方法必须返回valua和done两个值。

这样写的话每次都会很繁,好在ES6提供了generator(生成器)能生成迭代器,我们来看简化后的代码:

const obj = {
    name: 'helios',
    age: 23,
    [Symbol.iterator]: function* () {
        while (this.age <= 24) yield this.age++
    }
}

for (let it of obj) {
    console.log(it)
}

让object可迭代真的有意义么

知乎的这个回答是很有水平的了:为什么es6里的object不可迭代?

在stackoverflow中也有很高质量的回答:Why are Objects not Iterable in JavaScript?

在上面的回答中从技术方面说了为什么Object不能迭代(没有实现iterable),还说了以什么样的方式去遍历Object是个难题,所以把如何迭代的方式去留给了开发者。

但是还是要思考的一个问题就是:我们真有必要去迭代对象字面量么?

想一下我们要迭代对象字面量的什么呢?是keys还是values亦或者是entries ,这三种方式在ES6提供的新的数据类型map里面都有呀,完全是可以代替object的。在这里不说objectmap的区别,只是说说在ES6以后我们想把两个事物关联起来的时候,不一定要非得是用对象字面量了,map支持的更好一下。

对于什么时候用对象字面量(object)什么时候使用map我们可以做一下总结:

  • 对象字面量(object)应该是静 态的, 也就是说我们应该已经知道了里面有多少个,和对象的属性有什么

  • 使用对象字面量(object)的一般场景有:

    • 不需要去遍历对象字面量(object)的所有属性的时候
    • 我们知道了里面有多少个属性和对象的属性是什么的时候
    • 需要去JSON.stringifyJSON.parse时候
  • 其他的情况用map,其他的情况包括:

    • key不是字符串或者symbol的时候
    • 需要去遍历的时候
    • 要得到长度的时候
    • 遍历的时候对顺序有要求的(对象字面量(object)可能不是按照你写的顺序)

也并不说是map就肯定比对象字面量(object)好,map也有如下的缺点:

  • 不能使用对象解构
  • 不能JSON.stringify/JSON.parse

参考

浅谈Docker的核心概念和原理(中)

rootfs

容器就是一个进程,已经在宿主机上对容器进行了隔离,对容器资源进行限制,那么容器的文件系统呢?

容器的文件系统也应该和network/PID一样和宿主机进行隔离,这个就要用到了Mount Namespace了。

看一下在DOCKER基础技术:LINUX NAMESPACE(上)的一个程序:

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
 
/* 定义一个给 clone 用的栈,栈大小1M */
#define STACK_SIZE (1024 * 1024)
static char container_stack[STACK_SIZE];
 
char* const container_args[] = {
    "/bin/bash",
    NULL
};
 
int container_main(void* arg)
{
    printf("Container - inside the container!\n");
    /* 直接执行一个shell,以便我们观察这个进程空间里的资源是否被隔离了 */
    execv(container_args[0], container_args); 
    printf("Something's wrong!\n");
    return 1;
}
 
int main()
{
    printf("Parent - start a container!\n");
    /* 调用clone函数,其中传出一个函数,还有一个栈空间的(为什么传尾指针,因为栈是反着的) */
    int container_pid = clone(container_main, container_stack+STACK_SIZE, CLONE_NEWNS  | SIGCHLD, NULL);
    /* 等待子进程结束 */
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}

我们通过clone系统调用创建了一个新的子进程container_main,并且声明要为它启动Mount Namespace(CLONE_NEWNS)。

小知识🈯️:

为什么Mount Namespace对应的宏叫做CLONE_NEWNS而不是CLONE_NEWMOUNT的呢,这是因为Mount Namespace是Linux中的第一个namespace,当时以为就这一个了,所以也就这样了。

编译:

# gcc -o ns ns.c
# ./ns

Parent - start a container!
Container - inside the container!

这时候我们的shell就在container_main这个进程里面,我们可以通过ps看到只有这个namespace的进程。但是我们通过ls /或者ls /tmp发现文件系统并没有做隔离,还都是宿主机的文件系统。

这个是因为容器进程会默认继承父进程的文件系统,所以创建新进程的时候还有显示去声明那些目录要重新mount,比如我们要重新挂在/tmp这个目录,所以子进程的代码可以改为:

int container_main(void* arg)
{
    printf("Container - inside the container!\n");
    mount("none", "/tmp", "tmpfs", 0, ""); // 告诉容器以tmpfs(内存盘)的格式重新挂在/tmp目录
    /* 直接执行一个shell,以便我们观察这个进程空间里的资源是否被隔离了 */
    execv(container_args[0], container_args);
    printf("Something's wrong!\n");
    return 1;
}

编译之后我们通过ls /tmp查看这个目录下面就没有什么文件了。
image

我们每次启动容器,大多时候不是想继承父进程的文件系统,我们首先想到的就是直接把根目录(/)重新挂在了不就完了么。

在Linux中的chroot就能帮助搞定这个事情,chroot的用法比较简单,

通过chroot就能把ubuntu/centos的文件系统挂在到容器的根目录。这个就是rootfs了。

由于rootfs打包的不仅仅是应用程序,而是将整个操作系统的文件和目录一起打包进去,这也就意味着应用和它的依赖,这也就解决了云端和本地的配置不一样的问题了。

rootfs的分层

image

分为三层:只读层(readonly + whiteout)init层(ro + wh)可读写层(rw)

联合文件系统(UnionFS)

ubuntu和centos用得不一样

docker的volume在这里面说一说

参考

【翻译】52个思考之一: 不同种类的处理器

原文传送门

不同的处理器

这是这个系列博客中的第一篇,目的在解决‘每个博士生都应该知道的52个问题’以便更好的学习密码学。这系列问题已经被编写完成,目的是让博士候选人知道在第一年结束的时候他们应该知道自己应该知道什么-那些早起对博士生提醒的制度最后能尽快的废除。无论如何,我们将要在明年呈现问题的答案,我已经自愿完成了这个博客系列的第一件‘思考’。这第一个问题是讨论计算机体系结构和引出了下面几个问题

下面的几个有什么区别

  • 通用处理器
  • 带有指令集扩展的通用处理器
  • 专用处理器(协处理器)
  • 现场可编程门阵列(FPGA)

通用处理器没有严格的定义,人们普遍的认为如果处理器是图灵完备的那么这个处理器就是通用的。这就捕获了能够计算任何可以计算内容的处理器(例如: 能够解决图灵机器解决的问题)。我不想钻研图灵机器的定义,如果你觉得我这样不求甚解不好那么我建议你去研究计算理论[1]。注意到这个定义中没有性能和指令集合的概念,事实上,一些研究者已经经过复杂的证明过程证明过了,你可能只需要一条指令就能完成图灵计算[2]。在现代处理器的背景下,大多数可编程的CPU被认为是通用的。

通用的目的往往带来性能上的损失。特别的,通用处理器能够计算任何能够计算的东西,但是它从来不能在重复并且复杂的任务中表现出色。鉴于在各个应用的通用处理器上跑定期重复的任务,处理器的设计者可能整合指令集的扩展到微架构为了适应任务。从功能上讲,微架构功能可能没有区别但是最终用户可能会收获到巨大的性能提升。

因为在座的都是密码学的研究者,我将依照一个密码学的例子去讲指令集扩展。考虑到具有AES加密的台式机。从二级存储读取任何东西都需要CPU中断才能缓存之前解密的数据。鉴于从为命中缓存的硬盘访问被认为是比较可怕的,在顶部增加加密程序和你的瓶颈值得你再次考虑你的硬盘加密程序。在这里应该非常清楚AES是复杂的可重复任务,拥有一个带有简单指令集的通用处理器CPU,我们没有选择只能将解密实现为线性操作。Intel和AMD都认识到对磁盘加密的需求以及AES增加二级存储访问的负担,并且已经生产了AES-NI x86指令集扩展来加速在台式机上磁盘的加密速度。

如果你想完全加速各种运算,最佳的解决方案就是使用专用处理器或者使用专用集成电路(ASIC)。我们失去了通用处理器授权的灵活性这一特征,以便换取性能上的提升。这些类型的处理器经常紧密耦合在通用处理器中,因此称为协处理器。注意到,一个协处理器经常与通用处理器封装在同一个包当中,但不一定要集成到处理器的架构中。如果我们转向现代处理器的架构,Intel和AMD已经把声卡,图形处理器以及DSP引擎集成到CPU中一段时间了。附加功能通过专用寄存器公开,协处理器作为通用处理器必须管理的独立组件。

最后我们转向介绍现场可编程门阵列(FPGAs),这个是专用集成电路(ASIC)和通用处理器的中间选择。如果一个应用需要高性能的吞吐了量并且需要频繁的进行变更,那么FPGA是一个很好的选择。为了更好的理解FPGA的工作原理,考虑到有上千个逻辑门的面包板(breadboard),这个面包板上放置着数千个逻辑门和查找表。如果你将应用描述为一组门和时间约束,那么你可以在面包板上将他们连接在一起,并生成一个能够评估你应用程序的电路。一个FPGA得到了可再次编程的便利性,同时提供专用逻辑去评价目标应用。FPGA与通用程序的关键区别在于如何设计和构建你的应用。为了更加充分的利用你的硬件,你必须将你的应用程序描述为一组硬件组件和用硬件描述语言的事件(Verilog or VHD)。在成产之前,这个过程长用在FPGA上对通用和专用处理器进行原型设计。然而,这并不是没有任何缺点。使用低级构建块设计程序对于大型应用来说变得非常麻烦。除此之外,与通用嵌入式IC相比,能源消耗和硬件成本变得更高。最近,FPGA制造商Xilinx开始向提供有ARM通用内核并且集成在一个包中的FPGA[3]。现在,FPGA可用于ARM内核就像协处理器一样灵活。因此,你能够构建专用的逻辑去评价你的加密原语,因此能够加速加密应用程序。

总结,通用处理器是具有能计算任何可以计算的东西的能力。类似地,对于具有指令集扩展的通用处理器,能够在特定的应用中拥有很好的表现。专用处理器或者协处理器对于特定的任务是非常快的,但是它不能计算其他的任何东西。FPGA可用于构建上述硬件,但是牺牲了ASIC解决方案的灵活性。

翻译: typescript 2.7中interface和type(Interface vs Type alias in TypeScript 2.7)


首发地址


原文: Interface vs Type alias in TypeScript 2.7

译者注:

  • type alias翻译为类型别名
  • interface 不做翻译

image

经常有人在网上,在工作中,甚至在滑板公园询问我,在Typescript中定义编译时类型的类型别名interface有什么区别。

我以前做的第一件事就是让他们去看Typescript的手册。。。

不幸的是在大多数时候,他们不能找到他们想要找到的东西(隐藏在“高级类型”部分)。即使你能找到它,这个信息描述的是过时的(描述的行为指针对[email protected]版本)。

好消息各位!你不必在看了,这篇文章是关于何时使用interface类型别名的最新描述和风格指南。

官方的文档说:

"类型别名 可以和interface关键字一样,然而他们有一些细微的差别。"

这是对的

有什么不同呢

第一个不同

*interface*能够创建一个新的类型,*类型别名*不能创建一个新的类型

--例如:错误信息不能使用*类型别名*

这是不对的!(自从Typescript 2.1开始)

我们分别通过类型别名interface定义编译时类型Point,并且实现使用interface类型别名的类型参数实现两种getRectangleSquare

image

类型别名 和 interface 声明的Point

image

使用类型别名和interface定义getRectangleArea函数的参数

image

类型别名 和 interface声明的有相同的错误

两个相同的错误如下:

// TS Error: 
// Interface:
Argument of type '{ x: number; }' is not assignable to parameter of type 'PointInterface'. Property 'y' is missing in type '{ x: number; }'.
// Type alias:
Argument of type '{ x: number; }' is not assignable to parameter of type 'PointType'. Property 'y' is missing in type '{ x: number; }'.

第二个不同

"第二个更加重要的重要的区别是类型别名不能被继承或者实现"

这个同样也是错的

我们能够通过一个interface继承类型别名:

image

interface 继承类型别名

或者我们使用类型别名来实现类:
image

类实现类型别名。

或者一个类能够实现继承了类型别名interface
image

ThreeDimension 继承了PointType。PointType是类型别名声明的。

我们也能通过组合类型别名interface去实现类。
image

class 实现了interface和类型别名

第三个不同

"3. 类型别名 不能继承/实现 其他的类型别名"

当然这个也是错误的

  • 嗯,这个是部分正确的,但是这个表述具有误导性。

类型别名能通过交叉类型运算符**&**扩展interface或者任意有效的Typescript类型(它类似字典或者javascript对象,非原始类型)。

image

类实现了具有交叉类型的*类型别名*

我们也能利用映射类型实现interface类型别名的各种类型转换。

让我们通过Partial映射类型把ShapePerimeter变为可选的。
image

类实现了通过交叉运算符和类型映射定义的*类型别名*。`perimeter()`和`area()`是可选的以至于我们没必要在类中去实现他们。

弱类型检测也正常工作

image

弱类型检测按期望的一样工作。

类型别名 和 interface的混合类型

你可能偶尔想要定义一个可以充当一个具有额外属性对象或函数的对象。

我们这里讨论的是定义函数(可执行对象),和该函数的静态属性。

当与第三方库进行交互时,可以看到这种模式,这充分描述了类型的“全貌”

混合类型的定义和实现。

它和类型别名一样工作!

通过类型别名定义混合类型。

但是有一个非常微妙的不同。你将要在IDE中得到具体的类型信息去代替Counter类型。

image

使用类型别名和混合类型的interface的区别。

通常一个好的实践,是将我们的混合定义分为两个部分:

  • 可调用对象(函数)类型别名

  • 静态属性对象描述

image

  • 和最终的Counte类型

image

所以类型别名和interface有什么区别呢?

1. 不能使用通过类型别名定义联合类型去实现类

这将要在编译的时候触发一个错误:

image

第一点不同——联合运算符定义的类型不能被实现

这完全有道理!一个图纸不能实现两个结构类型中的一个,所以在这方面没有什么好惊讶的。

类型别名联合使用用于定义对象是有意义并且有效的。所以下面会在编译时报一个错误,因为对象必须去定义perimeter()area()两个中的一个。

image

联合类型——正确的使用对象字面量

2. 不同通过类型别名定义的联合运算符继承interface

image

第二个不同——联合定义类型不能被`interface`继承

同样,这个类的实现相似, interface是一个“静态”图纸——它不能实现两个结构中的一个,所以它不能被联合类型合并继承。

3. 类型别名关键字不能声明合并

interface有声明合并,但是类型别名没有。

什么是声明合并?

你能定义多次相同的interface,这些定义将要合并为一个。

image

声明合并

这种方式对于类型别名就不成立,因为类型别名是独一无二的实体(对于全局和模块域)。

image

第三个不同——类型别名不支持声明合并。

当我们为没有使用Typescript创作的库写第三方环境定义的时候,通过interface的声明合并是非常重要的,如果一些定义没有的话,使用者可以选择性的扩展它。

如果我们的库是使用Typescript写的,并且自动生成环境定义则同样使用。

这是唯一的用例,你应该总是使用interface而不是类型别名。

我应该如何使用React的Props和State?

一般的,你要使用的一致就可以(无论使用类型别名还是interface),就我个人而言,我还是推荐使用类型别名

  • 书写起来更短type Props = {}
  • 你的用法是统一的(你不用为了类型交叉而interface类型别名混用)
// BAD
interface Props extends OwnProps, InjectedProps, StoreProps {}
type OwnProps = {...}
type StoreProps = {...}
// GOOD
type Props = OwnProps & InjectedProps & StoreProps
type OwnProps = {...}
type StoreProps = {...}
  • 你组件公开的props/state不能被动态替换(译者注:因为原文作者这里说的
    monkey patched翻译为动态替换 what-is-monkey-patching),就这个原因,你组件的使用者就不能利用interface的声明合并。对于扩展应该有像HOC这样明确的模式。

总结

在这遍文章中我们学到了在Typescript 2.7interface类型别名的不同。

有了这个,我们得出在特定的场景中应该如何去定义编译类型的结论。

让我们来回顾一下:

  • 类型别名 能像interface一样,但是他们有三个重要的区别(联合类型,声明合并)
  • 使用适合你或者你团队的东西,但是要保持一致
  • 当你写个库或者定义第三方环境类型的时候,在公开的API中总是使用interface
  • 在你的React组件的state/props中考虑使用类型别名

浅谈POD的相关原理

为什么会有POD

  • Linux中进程组的映射
  • 容器设计模式(pod是共享某些资源的容器)

infra的作用是在pod中担任各种Namespace的基础(infra容器负责创建Namespace,该POD的其他容器加入Namespace)

image

对于一个POD里面的容器A和容器B来说:

  • 可以通过localhost进行通信
  • 看到的网络设备和infra容器一样
  • 一个pod只有一个ip地址,也就是这个 Pod 的 Network Namespace 对应的 IP 地址
  • 当然,其他的所有网络资源,都是一个 Pod 一份,并且被该 Pod 中的所有容器共享
  • Pod 的生命周期只跟 Infra 容器一致,而与容器 A 和 B 无关

sidercar容器

POD使用可哪些Namespace

参考

2018下半年计划以及分月计划

长远计划

计划进度

到2019年1月1日前应该完成的事情:

读书类

必选

  • 读完《区块链:技术驱动金融》8,9月份
  • 读完 《区块链革命:比特币底层技术如何改变货币、商业和世界》10, 11月份
  • 读完一半《HTTP权威指南》8,9月份

选修

  • 《深入浅出密码学——常用加密技术原理与应用》
  • 《通往财富自由之路》

博客等

必修

选修

视频等

必看

选看

  • 四大维度解锁 Webpack 前端工程化(需要买)
  • Node.js入门到企业Web开发中的应用
  • 让你页面速度飞起来 Web前端性能优化
  • 腾讯大牛亲授 Web 前后端漏洞分析与防御技巧

技术和编程

必搞

  • 开发自己的个人博客 10月开始
  • 开发区块链DAPP 九月开始

选搞

  • 函数式编程
  • 进阶CSS
  • 密码学

工作中的ER和Redux的区别

总结一下公司内部的状态/数据管理工具ER和Redux的区别

ER和Redux都是遵从的单向数据流的**,单向数据流也是比较适用于React的。工作中主要使用的ER以前是用的Redux,下面就对这两种框架进行总结一下

Redux

Redux中定义了一个个的Action用它来表达有事情发生,但是怎么发生,转变为下一个state要通过reducer去表达,所以reducer的作用就是表达了要做什么事情,然后变为的状态会存储在唯一的store中,然后把store注入到App中,然后App中又能通过触发action转变为下一个状态。用图示就是下面这样子:
dddbc43c-345e-44d7-ad0f-359a4f0ca11a

ER

ER是内部使用的工具,大致**如下:数据状态的管理都存放在Model里面,然后通过propsModel里面的数据注入JSX当中,然后再jsx中能够通过dispatch去触发改变Model里面的数据,图示如下:
5643b3bb-1621-4aa0-8943-86553603a17c

2018年终提前总结

2018年发生的事

(这里说一下去年的时候还应该写一个2017的总结,但是没写)

今天不知道怎么突然想写一篇今年的总结。可能是最近经历的太多了吧。几个月前的七月份结束了自己十几年的学生生涯,完成了从学生到社会人的转变,可能这种转变并不是彻底的,因为毕竟还没完全脱去身上的书气。
今年的这种转变堪比2014的从高中到大学的转变,那个时候还挺惆怅,离开生活了将近二十年的故乡,来到了毛主席的故乡长沙。刚来到时候感觉还不是很大,还和室友同学聊聊天。但是人在异乡,夜深人静的时候难免会思考人生,不断的回想高中美好时光:和朋友上课看电影,晚上下课去撸串之类。也曾哭过几次,但这个都不属于2018年发生的时候,应该在大学的流水账里面去说。

## 时间顺序
下面就按照时间顺序来说说今年发生的几件事:

### 在校招公司实习三个月

### 在学校的完成学生的收尾工作

### 上班

### 她结婚了

还是按照另一个维度,从学习,工作,生活,感情四个方面来对今年做一个总结:

学习

现在没有了上学时候的热情(待完善)

工作

做的都是小feature,没有难度(待完善)

生活

有没有生活???
说下今年在学校的生活和上班生活后的对比(待完善)

感情

她结婚了,55555(待完善)

今年的感受

呼应一下开头,说一下现在的感受。

总想让别人知道自己会/有什么

现在和上学的时候或者刚参加工作两个月的时候发的朋友圈的数量明显变少,或者说不知道发朋友圈给谁看了。现在感觉上学的时候发的那么多朋友圈真的都好傻,有的甚至都想删掉,幸亏微信出了一个把这条朋友圈对所有人不可见的功能。感觉当时就是由于虚荣心,去个地方旅游或者和朋友逛个街吃个饭看个电影啥的就希望所有人都能看到。

这个其实我想到电视剧(好像是武林外传)里面的对话。大致是这个意思:当一个在一方面很有造诣的时候,别人说你在这个方面不行你可能会不屑一顾;如果你在一个领域是半吊子,并且光和别人说你在这个领域的造诣的时候,别人说你在这个领域不怎么怎么着的时候你就会表现特别异常。

这个我在现在的工作中也遇到过这样的人,我也挺佩服她能说出来的:我上班来的时候就觉得旁边的QA妹子怎么光想着开RD的代码呢,还光想着从RD的代码中自己找出bug在哪,我当时真的想和她说先把本职工作做好,不要刷存在感。后来我在她旁边吃饭的时候,听到她和另一个同事说:“人就是约没有什么,越想让别人觉得自己有,就像我一样,我每次写一点代码就恨不得让所有人都知道,但是事实上我是不会写的”。自从这个之后我对这个妹子的就没有任何偏见了。

太自卑

没有自信这个我感觉体现在各个方面

工作学习方面

自信真的很重要,我在三个公司实习过,发现面试官的水平,准确的说一面面试官的水平并不会比自己高出来多少,有时候他们问的问题其实都差不多会,但是就是觉得他们是面试我们的人肯定比我们牛逼所以不敢发表我们的观点。

到了工作的时候我的感受也是这样,对于一个leader总是觉得他们高高在上,肯定是不会和他们成为朋友的,和他们肯定就是上下级的关系,觉得自己低人一等。其实我一直想克服这种问题,但是还是很难去从根上克服。

体育方面

拿羽毛球和篮球来说吧。

学东西没有热情

(待完善)

Kubernetes中的几种控制器

控制器

这里列出了,k8s中的所有控制器,它们都是遵循的都是控制器模式,控制器模式说白了就是控制循环,如下:

for {
    实际状态 := 获取集群中对象 X 的实际状态Actual State期望状态 := 获取集群中对象 X 的期望状态Desired Stateif 实际状态 == 期望状态{
        什么都不做
    } else {
        执行编排动作将实际状态调整为期望状态
    }
}

下面介绍几种控制器

Deployment

StatefulSet

DaemonSet

Job/CronJob

Cutsom

编写不用redux的React代码

背景

最近把同事的项目接了一下,准备按照自己的想法重构一遍的时候发现自己的写或者重构React代码的时候还是没有代码标杆。有的时候按照想法A写了一下,不知不觉的有改到了另一种格式。

业务场景

业务属于不算复杂的TO B业务的后台管理系统。如下几个图:

列表图

user

详情图

user-detail
点击租户绑定中的修改的时候,弹出一个modal框进行进行修改资源,如下图:
user - detail - re su

所有的管理界面基本都是大同小异,先进列表页(列表页中包含新建等相关操作),然后详情页面,详情页面能对某个东西进行修改。

技术栈

因为在项目中虽然引用了Redux但是从来没有使用过,所以现在的技术栈为Webpack+React+Typescript+React-router(+Redux)为什么在这里给redux加上个括号,是因为现在有给项目加上Redux的打算。
脚手架是同事搭建的,针对公司业务的。自己有一套脚手架的好处就是有技术或者组件沉淀的时候就可以打包在脚手架里面。
组件库是基于rc-component封装了一套业务组件。有的业务线使用的antd

技术架构

这里用技术架构可能有点高大上,但是实在想不出什么朴实的词汇。

虽然现在没有使用redux,但是还是借鉴了Redux中有状态组件和无状态组件的**。
有状态组件负责:

  • 调用接口获取数据
  • 存储数据
  • 处理用户响应(来更新数据)

无状态组件负责:

  • 展示数据
  • 向父组件传递实践响应
  • 存储必要的state(比如详情页面中,租户绑定[不要关心租户是什么概念],就是一个组件,修改按钮的modal的展示就放在这个组件中进行维护)

项目目录如下:

├── Makefile
├── README.md
├── mock
│   └── hello
├── package-lock.json
├── package.json
├── pre-commit
├── project.config.js
├── src
│   ├── app
│   │   ├── action.ts
│   │   ├── app.less
│   │   ├── app.tsx
│   │   ├── dao
│   │   │   ├── user.ts
│   │   │   └── workspace.ts
│   │   ├── reducer
│   │   │   └── index.ts
│   │   ├── root-reducer.ts
│   │   ├── root-router.tsx
│   │   ├── type
│   │   │   └── index.ts
│   │   └── type.ts
│   ├── common
│   │   ├── component
│   │   │   ├── form
│   │   │   │   ├── custom-validator.ts
│   │   │   │   └── index.tsx
│   │   │   └── user-form-dialog
│   │   │       ├── index.tsx
│   │   │       ├── user-form-dialog.less
│   │   │       └── user-form-dialog.less.d.ts
│   │   ├── css
│   │   │   └── global.less
│   │   ├── schemas
│   │   │   └── user.ts
│   │   ├── conf
│   │   │   └── validation
│   │   │       ├── user-list.ts
│   │   └── util
│   │       ├── field-value.ts
│   ├── index.html
│   ├── types
│   │   ├── classnames.d.ts
│   │   ├── lodash.chunk.d.ts
│   │   ├── rc-select.d.ts
│   │   └── validator.d.ts
│   ├── user
│   │   ├── create-ldap
│   │   │   ├── component
│   │   │   │   └── ldap-form-dialg
│   │   │   │       ├── index.tsx
│   │   │   │       ├── ldap-form-dialog.less
│   │   │   │       └── ldap-form-dialog.less.d.ts
│   │   │   ├── create-ldap.less
│   │   │   ├── create-ldap.less.d.ts
│   │   │   └── index.tsx
│   │   ├── create-user
│   │   │   ├── create-user.less
│   │   │   ├── create-user.less.d.ts
│   │   │   └── index.tsx
│   │   ├── user-detail
│   │   │   ├── index.tsx
│   │   │   ├── user-detail.less
│   │   │   └── user-detail.less.d.ts
│   │   ├── user-ldap
│   │   │   ├── component
│   │   │   │   └── ladp-detail
│   │   │   │       ├── index.tsx
│   │   │   │       ├── ldap-detail.less
│   │   │   │       └── ldap-detail.less.d.ts
│   │   │   ├── index.tsx
│   │   │   ├── user-ldap.less
│   │   │   └── user-ldap.less.d.ts
│   │   └── user-list
│   │       ├── index.tsx
│   │       ├── user.less
│   │       └── user.less.d.ts
├── static.d.ts
├── tsconfig.json
├── tslint.js
└── typings
    ├── globals
    │   └── index.d.ts
    └── index.d.ts

介绍一下几个非业务的目录:

app

这个目录的定位是工程的入口文件。主要有合并reducer定义redux中action type以及配置路由

common

这么目录是存放一些通用的东西,包括但是不限于:

  • component: 通用组件
  • css: 全局的css
  • conf
    • validation: 填写表单时候的字段校验
  • utils:常用通用方法
  • schemas: 定义typescript的要用到的通用类型

types

针对于typescript来讲的如果没有@type对应的组件库,就要自己实现一下。但是公司里这个文件一般是这个样子的:

declare  module 'rc-select' {
    const Select: any;
    export default Select;
}

对,就是这么简单粗暴的声明。

其他的就都是业务目录的。

开发模式

公司的业务分为不同的产品,每个产品在不同的repo下开发,我负责的这个产品算我在内有两个人开发,主要的东西还是我一个人开发。

开发模式采用当下比较流行的Feature Branching的开发方式,即使每个feature或者bugfix分为不同的分支,等开发完成之后向主分支(现在是develop是主分支)提一个pull request。然后develop分支就会触发CICD的流程,部署到开发环境,来看线上的效果。

但是这种开发模式,遇到了一个问题:

A分支是1.0版本的上线分支;
develop是目前的开发分支;
每当A分支上有bug的时候会开一个新的分支去修复bug,然后把这个分支在merge到develop分支上来保证bug也在开发分支上进行了fix,
但是--------------------------
develop分支进行很大的重构,文件目录也进行了很大的变化。
当A分支上又有一个bug的时候,就不能通过pull request的方式,简单merge回develop分支了。
就要一份代码还要在develop上在copy一次。

业务组件的架构

真的不想叫架构这么高大上的名字,也不能叫文件目录的放置吧。

上文也是说到了分为有状态组件和无状态组件。
按照user这个目录来说:

user
├── component
│   ├── user-detail
│   │   ├── index.tsx
│   │   ├── user-detail.less
│   │   └── user-detail.less.d.ts
│   ├── user-detail-ak
│   │   ├── index.tsx
│   │   ├── user-detail-ak.less
│   │   └── user-detail-ak.less.d.ts
│   ├── user-detail-base-info
│   │   └── index.tsx
│   ├── user-detail-resource
│   │   ├── index.tsx
│   │   ├── user-detail-resource.less
│   │   └── user-detail-resource.less.d.ts
│   ├── user-detail-workspace
│   │   └── index.tsx
│   └── user-form-dialog
│       ├── index.tsx
│       ├── user-form-dialog.less
│       └── user-form-dialog.less.d.ts
├── create-ldap
│   ├── component
│   │   └── ldap-form-dialg
│   │       ├── index.tsx
│   │       ├── ldap-form-dialog.less
│   │       └── ldap-form-dialog.less.d.ts
│   ├── create-ldap.less
│   ├── create-ldap.less.d.ts
│   └── index.tsx
├── create-user
│   ├── create-user.less
│   ├── create-user.less.d.ts
│   └── index.tsx
├── user-detail-wrapper
│   └── index.tsx
├── user-ldap
│   ├── index.tsx
│   ├── user-ldap.less
│   └── user-ldap.less.d.ts
├── user-ldap-detail-wrapper
│   ├── index.tsx
│   ├── user-ldap-detail-wrapper.less
│   └── user-ldap-detail-wrapper.less.d.ts
└── user-list
    ├── index.tsx
    ├── user.less
    └── user.less.d.ts

并没有reduxcontainers的目录,因为感觉到并没有使用redux,所以就没有使用containers的概念。
但是也是可以把user中的组件分为两种组件,一种是component的里面的组件,另一种是非component中的组件。^__^
component组件承担是无状态组件的责任,非component组件承担的是有状态组件的责任。
虽然这个模式规范一致在心中,但是实现的时候难免会一些偏差。

遇到的偏差

遇到的问题就是:心中的这份规范并不是很明确
第一次重构代码完,一个有状态组件的render函数如下:

render() {
    return (
      <Fragment>
        <div className={style.header}>用户管理</div>
        <AdvancedTable
          onLoad={(params: IParams) => this.handleLoad(params)}
          listOutOfDate={this.state.listOutOfDate}
          toolbar={this.toolbarProps()}
          table={this.tableProps()}
          pagination={{ total: this.state.total }}
        />
      </Fragment>
    );
  }

后来当我重构另一个功能的时候一个有状态组件的render函数是下面这样的:

render() {
    const { total, list } = this.state;
    return (
      <ResourceList
        total= {total}
        onLoad={(params: IParams) => this.onLoad(params)}
        onAdd={() => this.onAdd()}
        jump={(yarn: IYarnEntity) => this.jump(yarn)}
        list={list}
        title="Hadoop管理"
        cols={this.columns}
      />
    );
  }

后面这份代码和上面的代码相比来说组件抽象的组件更加完全。
第一个组件虽然做到了有状态组件的功能,但是还是不够,因为Table组件传递的参数还是在这个有状态组件中传递的;

第一个组件中的<div className={style.header}>用户管理</div>还是应该在是无状态组件管辖的范围。

结果

现在的结果就造成了,看一个月之前的代码看着就很不舒服,又想着慢慢重构。
本来想着这么做都是为了后面引入redux的做铺垫,但是现在这个感觉铺垫的时间要加长了。

2018年八月份计划

计划列表

  • 看完“Django打造上线标准的在线教育平台”剩下的视频
  • 翻译三到四篇密码学的文章
  • 读一半《区块链:技术驱动金融》(如果书到位的话)
  • 读《HTTP权威指南》 (应该是下半个月的事情了,书还在家里了)
  • 看完“Python3实用编程技巧进阶 ”这个视频

要解决的问题

  • redux怎么进行异步,并且实现一下子。

上个月未完成原因

因为七月的第四周参加了个比赛,所以学习的心失去了一大部分,周末没有学习,只是看了一章左右的视频,并把任务延续到八月份。

新老接口

branch

描述 现在 github suggest method 进入
删除分支 /branch/delete /teams/:id /branch/:id delete 点击进入
更新分支 /branch/update /teams/:id /branch/:id patch 点击进入
查询分支 /branch/query /teams/:id /branch/:id get 点击进入
添加分支 /branch/add /orgs/:org/teams /branchs/:branch post 点击进入

member

现在 github suggest method 进入
/member/delete /orgs/:org/members/:username branchs/:branch/member/:id delete 点击进入
/member/update /orgs/:org/memberships/:username branchs/:branch/member/:id put 点击进入
/member/query /orgs/:org/members/:username branchs/:branch/member/:id get 点击进入
/member/add /orgs/:org/memberships/:username branchs/:branch/member/:id post 点击进入
/member/invitation /orgs/:org/invitations branchs/:branch/invitations post

2017实习总结

实习总结

这周末就实习结束了,也没有什么事情了,就对这次实习进行一下总结,实习时间是从2017年6月到2017年十月底,四个多月的时间。

刚开始以为每周或者每个月写一次实习总结,发现还是有点懒惰再有就是工作后来渐渐的忙了起来,剩余的时间有点少了。所以就这次集中起来写一次吧。

熟悉环境

刚来的时候发现百度这里环境真的是不错,不是几个人坐在一个长排的桌子上,而是每个人都有一个自己的小独立空间,简单如下图所示:
百度工位图
然后部门的领导给我分配了导师叫swf(现在也成为好朋友了),我放下包收拾了一下,他带我去休息区7788的和我说了一些我听不懂话,我当时心里想:“怎么写代码之前要这么多事情,还什么卡片乱七八糟的,头好大”,但是现在也基本熟悉了流程。
因为是十点上班,所以这一套完了之后基本就快十二点了,所以就去吃饭了。发现食堂的环境还是很nice呀,各种风味都有,但是对于我这种穷酸学生来说还是比较贵的了,但是度美食还是比较实惠的,所以一直到现在我都去那里吃。这时候也上一张图,免得以后会忘记。
食堂的照片
食堂的饭

工作是不打卡制的,有的人来的早,早早的把工作做完了,在六点就可以走了。

熟悉工作内容

第一周主要是学习工作中要用到的技术以及公司的规范。按照新人wiki中流程走,最后要写一个新人作业,要技术厉害的人去review。

做了几次的新人作业,给我的感受就是公司用的技术还是相对比较落后,好多的东西都是用的内部的轮子,不是用的市面上比较流行的东西,这一点是让我当时比较不容易理解的,后来也慢慢的理解了,因为大公司的业务比较多,开发的时间也是很长的了,所以重构起来还是比较困难,所以要慢慢重构,我把前端代码拉下来的时候发现,光代码竟然有600+M

工作用到的技术

因为工作前也知道了在工作中使用的是react,所以在学校也学了一周左右的react,在工作用了react还是比较容易上手,适应工作又是另一部分了。

管理React组件通信数据的不使用的Redux/Flux,还是用的内部管理的数据的工具叫React-ER。关于React-ERRedux的区别,我已经在前面的文章提到过了工作中的ER和Redux的区别

用的babel的版本比较低,所以好多ES6的方法还是不能用的,所以经常会用的underscore

发送请求也不是用的当前比较流行的fetch,也是用的部门内部自己封装的ajax请求的一个库。

2019要做的事情

学习

  • 读七本技术书 + 三本非技术书(四百页以内,超过四百页的部分和另外超过四百页的相加超过二百页算一本)
  • 前端测试从知道到入门再到熟悉(使用jest + Enzyme测试React项目有一定自己的理解,最好能做到横向对比)
  • webpack从入门到熟悉(能到达写loader和plugin的地步)
  • React从半熟悉到熟悉(能说清react的原理,甚至能看一点源码)
  • redux/react-redux从理解到熟悉(能力redux为什么这个设计,和mbox去对比,甚至能看一点源码)
  • react-router 从理解到熟悉(理解react-router的设计的好处,看懂大部分源码)
  • typescript从理解到半熟悉(熟练运用,能解答处别人问的问题,和coffee script/flow等相同工具做横向的对比)
  • 学习微服务的一些基础知识
  • 不管啥技术,写一个自己用的理财APP

工作

  • 涨工资。。。
  • 更新前端组的脚手架(加测试,加热刷新)
  • 完善前端的组件库并写一片心得文章

生活

  • 目前81kg,目标76
  • 虽然没有啥钱但是还是要理财
  • 上班自己带饭
  • 学会装修自己,修修眉,多买点衣服(基于有闲钱的基础上)
  • 争取遇到自己喜欢的人,55555.你若盛开,蝴蝶自来,你若绽放,清风自来

vim常用快捷键

https://blog.csdn.net/donahue_ldz/article/details/17139361

普通模式

向下移动一个段

)

想下移动一个段(一般是一个大括号)

向上移动一个段

(

同上

移动到屏幕的某一行

gx
比如g0就代表移动到屏幕的第一行

移动到同一行的下一个/上一个字符串

fc
把光标移动到同一行的下一个字符c处
Fc
把光标移动到同一行的上一个字符c处

移动到行首

0

移动到某一行

:x

文件头部

gg

文件尾部

https://blog.csdn.net/donahue_ldz/article/details/17139361
G

向上翻一页

control + b

向下翻一页

control + f

向下翻半页

control + d

向上翻半页

control + u

屏幕中间

M

前端继续学习路径

TODO

基础类

  • 《JavaScript高级程序设计》
  • 《你不知道的JavaScript》系列
  • promise 迷你书

工程类

  • redux实践
  • 搞清楚react-router是啥子
  • 编译原理(YouTube)
  • 前端编译原理实践(网易云课堂
  • webpack

Typescript配合react-redux实践


Typescript配合react-redux实践

公司的技术栈是Typescript(下面可能称为ts) + React全家桶,从2018年的7月份入职开始接触Typescript。ts给我带来的感受是:用三个月和用一年基本看不出啥差别(不深度的去学习的情况下)

为什么使用状态管理工具

因为我接手的项目没有使用状态管理工具,在入职之后的前几个月,一直使用的父子组件传值的方式去处理状态。但是后来发现这种方式很难去复用一些东西,比如:

  • 当我在模块A中已经写了请求用户列表的代码,如果我们要在模块B,C,D中还想要这个用户列表那还得写一遍请求代码。如果使用了reduxdispatch一个action就可以了。

对此写了一遍文章为什么即将使用react-redux

如果想了解当时的代码结构可以看Typescript配合React实践

React的state/props/context是使用interface还是type

如果对于typeinterface还是云里雾里的话,可以看看这篇翻译:翻译: typescript 2.7中interface和type(Interface vs Type alias in TypeScript 2.7)。这篇文章的最后阐述了作者的观点,作者比较倾向于使用type去声明state/props/context

我和好多人讨论这个问题,也看过网上不少的讨论。我觉得对于这个问题来说就属于仁者见仁,智者见智了。
在本文中我还是使用的interface去声明state/props/context

项目架构

【翻译】使用Kubernetes API构建一些东西 - 使用GO

原文


文章首发

image

在本系列文章的第四部分将要使用官方client介绍K8S API的可编程性。本篇文章使用client-go去实践一个简单的PVC的watch程序,这个程序在前面的文章里面已经使用pythonjava实现过了。

kubernetes 的Go Client项目(client-go)

在进入代码之前,理解k8s的go client项目是对我们又帮助的。它是k8s client中最古老的一个,因此具有很多特性。 Client-go 没有使用Swagger生成器,就像前面我们介绍的openAPI一样。它使用的是源于k8s项目中的源代码生成工具,这个工具的目的是要生成k8s风格的对象和序列化程序。

该项目是一组包的集合,该包能够满足从REST风格的原语到复杂client的不同的编程需求。

image

RESTClient是一个基础包,它使用api-machinery库中的类型作为一组REST原语提供对API的访问。作为对RESTClient之上的抽象,clientset将是你创建k8s client工具的起点。它暴露了公开化的API资源及其对应的序列化。

注意:
在 client-go中还包含了如discovery, dynamic, 和 scale这样的包,虽然本次不介绍这些包,但是了解它们的能力还是很重要的。

一个简单的k8s client 工具

让我们再次回顾我们将要构建的工具,来说明go client的用法。pvcwatch是一个简单的命令行工具,它可以监听集群中声明的PVC容量。当总数到达一个阈值的时候,他会采取一个action(在这个例子中是在屏幕上通知显示)
image

你能在github上找到完整的例子

这个例子是为了展示k8s的go client的以下几个方面:

  • 如何去连接
  • 资源列表的检索和遍历
  • 对象监听

Setup

client-go支持Godep和dep作为vendor的管理程序,我觉得dep便于使用所以继续使用dep。例如,以下是client-go v6.0和k8s API v1.9所需最低限度的Gopkg.toml

[[constraint]]
  name = "k8s.io/api"
  version = "kubernetes-1.9.0"
[[constraint]]
  name = "k8s.io/apimachinery"
  version = "kubernetes-1.9.0"
[[constraint]]
  name = "k8s.io/client-go"
  version = "6.0.0"

运行dep ensure确保剩下的工作。

连接 API Server

我们Go client的第一步就是建立一个于API Server的连接。为了做到这一点,我们要使用实体包中的clientcmd ,如下代码所示:

import (
...
    "k8s.io/client-go/tools/clientcmd"
)
func main() {
    kubeconfig := filepath.Join(
         os.Getenv("HOME"), ".kube", "config",
    )
    config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
    if err != nil {
        log.Fatal(err)
    }
...
}

Client-go通过提供实体功能来从不同的上下文中获取你的配置,从而使之成为一个不重要的任务。

从config文件

正如上面的例子所做的那样,你能从kubeconfig文件启动配置来连接API server。当你的代码运行在集群之外的时候这是一个理想的方案。
clientcmd.BuildConfigFromFlags("", configFile)

从集群

当你的代码运行在这个集群中的时候,你可以用上面的函数并且不使用任何参数,这个函数就会通过集群的信息去连接api server。

clientcmd.BuildConfigFromFlags("", "")

或者我们可以通过rest包来创建一个使用集群中的信息去配置启动的(译者注:k8s里所有的Pod都会以Volume的方式自动挂载k8s里面默认的ServiceAccount,所以会实用默认的ServiceAccount的授权信息),如下:

import "k8s.io/client-go/rest"
...
rest.InClusterConfig()

创建一个clientset

我们需要创建一个序列化的client为了让我们获取API对象。在kubernetes 包中的Clientset类型定义,提供了去访问公开的API对象的序列化client,如下:

type Clientset struct {
    *authenticationv1beta1.AuthenticationV1beta1Client
    *authorizationv1.AuthorizationV1Client
...
    *corev1.CoreV1Client
}

一旦我们有正确的配置连接,我们就能使用这个配置去初始化一个clientset,如下:

func main() {
    config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
    ...
    clientset, err := kubernetes.NewForConfig(config)
    if err != nil {
        log.Fatal(err)
    }
}

对于我们的例子,我们使用的是v1的API对象。下一步,我们要使用clientset通过CoreV1()去访问核心api资源,如下:

func main() {
    ...
    clientset, err := kubernetes.NewForConfig(config)
    if err != nil {
        log.Fatal(err)
    }
    api := clientset.CoreV1()
}

你能在这里看到可以获得clientsets。

获取集群的PVC列表

我们对clientset执行的最基本操作之一获取存储的API对象的列表。在我们的例子中,我们将要拿到一个namespace下面的pvc列表,如下:

import (
...
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func main() {
    var ns, label, field string
    flag.StringVar(&ns, "namespace", "", "namespace")
    flag.StringVar(&label, "l", "", "Label selector")
    flag.StringVar(&field, "f", "", "Field selector")
...
    api := clientset.CoreV1()
    // setup list options
    listOptions := metav1.ListOptions{
        LabelSelector: label, 
        FieldSelector: field,
    }
    pvcs, err := api.PersistentVolumeClaims(ns).List(listOptions)
    if err != nil {
        log.Fatal(err)
    }
    printPVCs(pvcs)
...
}

在上面的代码中,我们使用ListOptions 指定 label 和 field selectors (还有namespace)来缩小pvc列表的范围,这个结果的返回类型是v1.PeristentVolumeClaimList。下面的这个代码展示了我们如何去遍历和打印从api server中获取的pvc列表。

func printPVCs(pvcs *v1.PersistentVolumeClaimList) {
    template := "%-32s%-8s%-8s\n"
    fmt.Printf(template, "NAME", "STATUS", "CAPACITY")
    for _, pvc := range pvcs.Items {
        quant := pvc.Spec.Resources.Requests[v1.ResourceStorage]
        fmt.Printf(
            template, 
            pvc.Name, 
            string(pvc.Status.Phase), 
            quant.String())
    }
}

监听集群中pvc

k8s的Go client框架支持为指定的API对象在其生命周期事件中监听集群的能力,包括创建,更新,删除一个指定对象时候触发的CREATED,MODIFIED,DELETED事件。对于我们的命令行工具,我们将要监听在集群中已经声明的PVC的总量。

对于某一个namespace,当pvc的容量到达了某一个阈值(比如说200Gi),我们将会采取某个动作。为了简单起见,我们将要在屏幕上打印个通知。但是在更复杂的实现中,可以使用相同的办法触发一个自动操作。

启动监听功能

现在让我们为PersistentVolumeClaim 这个资源通过Watch去创建一个监听器。然后这个监听器通过ResultChan从go的channel中访问事件通知。

func main() {
...
    api := clientset.CoreV1()
    listOptions := metav1.ListOptions{
        LabelSelector: label, 
        FieldSelector: field,
    }
    watcher, err :=api.PersistentVolumeClaims(ns).
       Watch(listOptions)
    if err != nil {
      log.Fatal(err)
    }
    ch := watcher.ResultChan()
...
}

循环事件

接下来我们将要处理资源事件。但是在我们处理事件之前,我们先声明resource.Quantity类型的的两个变量为maxClaimsQuant totalClaimQuant 来分别表示我们的申请资源阈值(译者注:代表某个ns下集群中运行的PVC申请的上限)和运行总数。

import(
    "k8s.io/apimachinery/pkg/api/resource"
    ...
)
func main() {
    var maxClaims string
    flag.StringVar(&maxClaims, "max-claims", "200Gi", 
        "Maximum total claims to watch")
    var totalClaimedQuant resource.Quantity
    maxClaimedQuant := resource.MustParse(maxClaims)
...
    ch := watcher.ResultChan()
    for event := range ch {
        pvc, ok := event.Object.(*v1.PersistentVolumeClaim)
        if !ok {
            log.Fatal("unexpected type")
        }
        ...
    }
}

在上面的for-range循环中,watcher的channel用于处理来自服务器传入的通知。每个事件赋值给变量event,并且event.Object的类型被声明为PersistentVolumeClaim 类型,所以我们能从中提取出来。

处理ADDED事件

当一个新的PVC创建的时候,event.Type的值被设置为watch.Added。然后我们用下面的代码去获取新增的声明的容量(quant),将其添加到正在运行的总容量中(totalClaimedQuant)。最后我们去检查是否当前的容量总值大于当初设定的最大值(maxClaimedQuant ),如果大于的话我们就触发一个事件。

import(
    "k8s.io/apimachinery/pkg/watch"
    ...
)
func main() {
...
    for event := range ch {
        pvc, ok := event.Object.(*v1.PersistentVolumeClaim)
        if !ok {
            log.Fatal("unexpected type")
        }
        quant := pvc.Spec.Resources.Requests[v1.ResourceStorage]
        switch event.Type {
            case watch.Added:
                totalClaimedQuant.Add(quant)
                log.Printf("PVC %s added, claim size %s\n", 
                    pvc.Name, quant.String())
                if totalClaimedQuant.Cmp(maxClaimedQuant) == 1 {
                    log.Printf(
                        "\nClaim overage reached: max %s at %s",
                        maxClaimedQuant.String(),
                        totalClaimedQuant.String())
                    // trigger action
                    log.Println("*** Taking action ***")
                }
            }
        ...
        }
    }
}

处理DELETED事件

代码也会在PVC被删除的时候做出反应,它执行相反的逻辑以及把被删除的这个PVC申请的容量在正在运行的容量的总值里面减去。

func main() {
...
    for event := range ch {
        ...
        switch event.Type {
        case watch.Deleted:
            quant := pvc.Spec.Resources.Requests[v1.ResourceStorage]
            totalClaimedQuant.Sub(quant)
            log.Printf("PVC %s removed, size %s\n", 
               pvc.Name, quant.String())
            if totalClaimedQuant.Cmp(maxClaimedQuant) <= 0 {
                log.Printf("Claim usage normal: max %s at %s",
                    maxClaimedQuant.String(),
                    totalClaimedQuant.String(),
                )
                // trigger action
                log.Println("*** Taking action ***")
            }
        }
        ...
    }
}

运行程序

当程序在一个运行中的集群被执行的时候,首先会列出PVC的列表。然后开始监听集群中新的PersistentVolumeClaim事件。

$> ./pvcwatch
Using kubeconfig:  /Users/vladimir/.kube/config
--- PVCs ----
NAME                            STATUS  CAPACITY
my-redis-redis                  Bound   50Gi
my-redis2-redis                 Bound   100Gi
-----------------------------
Total capacity claimed: 150Gi
-----------------------------
--- PVC Watch (max claims 200Gi) ----
2018/02/13 21:55:03 PVC my-redis2-redis added, claim size 100Gi
2018/02/13 21:55:03
At 50.0% claim capcity (100Gi/200Gi)
2018/02/13 21:55:03 PVC my-redis-redis added, claim size 50Gi
2018/02/13 21:55:03
At 75.0% claim capcity (150Gi/200Gi)

下面让我们部署一个应用到集群中,这个应用会申请75Gi 容量的存储。(例如,让我们通过helm去部署一个实例influxdb)。

helm install --name my-influx \
--set persistence.enabled=true,persistence.size=75Gi stable/influxdb

正如下面你看到的,我们的工具立刻反应出来有个新的声明以及一个警告因为当前的运行的声明总量已经大于我们设定的阈值。

--- PVC Watch (max claims 200Gi) ----
...
2018/02/13 21:55:03
At 75.0% claim capcity (150Gi/200Gi)
2018/02/13 22:01:29 PVC my-influx-influxdb added, claim size 75Gi
2018/02/13 22:01:29
Claim overage reached: max 200Gi at 225Gi
2018/02/13 22:01:29 *** Taking action ***
2018/02/13 22:01:29
At 112.5% claim capcity (225Gi/200Gi)

相反,从集群中删除一个PVC的时候,该工具会相应展示提示信息。

...
At 112.5% claim capcity (225Gi/200Gi)
2018/02/14 11:30:36 PVC my-redis2-redis removed, size 100Gi
2018/02/14 11:30:36 Claim usage normal: max 200Gi at 125Gi
2018/02/14 11:30:36 *** Taking action ***

总结

这篇文章是进行的系列的一部分,使用Go语言的官方k8s客户端与API server进行交互。和以前一样,这个代码会逐步的去实现一个命令行工具去监听指定namespace下面的PVC的大小。这个代码实现了一个简单的监听列表去触发从服务器返回的资源事件。

下一步

从这一点开始,这系列文章将要继续使用client-go这个框架。在接下来的写作中,将要用控制器模式使用client-go 创建更加更加强大的client。

参考

2017年11月到2018年1月的规划

规划

首先要在十一月份最晚十一月底把工作确认下来。

2017年十一月

在工作中学习VUE,利用空余的时间,学习函数式编程争取能在十一月的时间理解函数式编程。

十一月应该会有三个周末,前两个周末(第二个周末可能会有一天去找同学)和工作之余看VUE相关的东西,第三个周末学习函数式编程。

2017年十二月

在工作之中继续理解VUE,争取能够在业务中使用函数式编程的**。

感觉就没有必要回家了,元旦再回家。

一共有四个周末:

  1. 第一周看函数式编程相关的东西
  2. 第二周看VUE的相关实践
  3. 第三周和第四周看CSS相关的书籍

2018年1月

继续在工作之中理解VUE,继续运用函数式编程。
元旦回家,最后一个周末辞职,应该就还剩下两个周末

两个周末都看《HTTP权威指南》

纪念自己第一次自由搏击比赛

昨天(2018/04/21)参加城市格斗联赛75KG的自由搏击比赛。周六比赛,教练周一告诉我去打75KG的,这是第一次登台难免会紧张。周二是拳馆的实战课,和一个初次见面的七十公斤级的打,暴露了我身上的很多的问题,在这里记录一下:

  1. 第一点也是最主要的,在比赛过程中,大脑是一片空白的
  2. 在累了之后,手不知道拿高,甚至挨了几个抡摆之后还不知道防守
  3. 身体站的太直,不能很好的同时保护头和肋骨
  4. 王八拳,没有看准就乱打,对方打过来几拳就想着还回去,想着先把防守做好或者先把对方推开
  5. 对于对方后腿(低扫),防守还是跟不上(意识要训练)
  6. 也是最可怕的一点,对于自己的体力太过于自信,刚开始打的太快以至于一分钟之后就累了

我也在脑子中想了一下上面的问题,在几天的训练中也有所改变,对于这次比赛暴露出来的问题我觉得还是很严重的,我以为迈上拳台是克服自己内心恐惧的第一步,其实不然,在比赛的过程中对手用的三次后蹬腿,第二次的时候结结实实的蹬到了我的肋骨,我当时就感觉有点呼吸困难,但是还是忍了下来,在后面中就开始害怕,我敢向前,也害怕对手再出后蹬把我KO,在拳台上恐惧对手这就已经输了,这次也是以失败结尾。比赛完教练说,对方就是胜在拳打的准,而我主要是靠腿得分,我的拳基本就是王八拳乱抡。总结一下这次暴露出来的问题:

  1. 这次还是好一点,有那么几秒在动脑子,比如说先出前手然后出后腿,看到自己手靠下的时候拿上去,也就这么两点了,希望以后继续多动脑子
  2. 这次想打击腹然后摆拳,但是全场都没做出这个动作,下台还被教练说是王八拳。主要还是因为上面的问题,没有看准就去打,一拳没打完就收回来了,
  3. 害怕,害怕,害怕。报名了怕上台,上台了怕被KO。如果第三局多出几个动作应该不会是这样的结果,但是没有如果

这次可能是因为对手出腿太慢了,每一次都被我挡住了,就主要是几个后蹬没有应对的策略,其实当时如果看出来是后蹬,踢一下他的前腿也就没事了。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.