IT 设备折腾记 - 显示器的坏点

iPhone X

东京银座的 Apple Store,银山银海,找一个欧美相工作人员,终于可以愉快的交流了,😂。

然后开始进入购买排队流程,那个小队都快排出店外了。本以为可以用公司的 Global EPP,结果查不到,好在免税。最终价格是不到 8K,为什么一购物日元就上涨呢?

Face ID 真不错,不再有手汗的困扰了,但应用场景也不像 Touch ID 那样广了,各有利弊。

不过自从有了一张电信的大大流量卡之后,就很渴望 iPhone 能出个双卡双卡,Apple Watch 3 的 eSIM 让人看到了希望。

DELL 显示器有了坏点

意外发现在左上角有一个坏点,不知什么时间的事。

这种问题,属于心理影响大于实际影响很多倍的典型案例。

不仔细看,几乎不会发现,但自从有了这个点,没事就会找一下,然后感觉就很不爽,继而更不爽。

不得不说,DELL 的服务还是很赞的,直接换了新机,不然真不知道如何处理。卖掉有点会掉价,不卖自己也不爽。

以后只买 DELL UltraSharp,恩!

其实生活中也是,好多事,有了心结,难过会大大超越事情本身。

Filco 87

是的,那块 87 键的垫板出价太低了,以致于舍不得,于是花了大一千块配了个 87 的键盘。

当然了,理由很多,双模,可用 fn,调节音量,屏幕。更好用的方向键。

多说无益,入就入了,这个品牌不会让人感到不值。

DJI OSMO+

说到不值,感受最深的就是 OSMO+,这个东西的使用场景太受限了,而且质量也不怎么好。

现在是放那贬值,想出出不掉。。。

以至于让我这个数码爱好者,都不再敢轻易对未测试新物下单。

显示器 - 4K? 5K

键盘不再纠结了,总要有个事惦记吧,于是显示器又上心头。

5K 太少了,价格高到不行,那就看看 4K 吧。但又总觉得 4K 用哪个分辨率都不行,开 HiDPI 不行,不开也不行。。。

也不是没有完美参数的,像 UP3218K,除了价格 :(

通过上面的参数来看,PPI 上 200,且价格能接受的,就只有 LG 5K 了,本想去 HK 背个回来的,那边的价格才 7xxx,心动。去 Apple Store 看了下,感觉好差,整个屏很不清晰,应该是垃圾桶带不动 5K 的缘故吧,做工也不是很精致,最主要的是丑,丑的对不起价格。边上的 iMac 5K 就超清晰,可惜不支持 TDM。

或者 U2718Q 吧,除了 4K,不支持 Type-C,其它都好 -^_^-

iPhone - 217 电池

终于约到了 Apple 的电池更换服务,周未去换了下,整个机器的性能、待机时间有着明显的提升。感觉 6s Plus 又可以再战两年。

工作人员把 iPhone 连上 WiFi,然后就可以用 iPad 来操作,进行检测,最后生成一个检测报告,那叫一个专业。

不要经常在 20% 以下电量使用,对电池损耗会小一些。

JavaScript - 循环和迭代

用了这么久的 JS,经常被它的循环方法绕晕,这几种方法的区别,优劣,在此做一个详细总结。

  • for
  • while
  • forEach
  • for…in
  • for…of
  • map


🥈 for / 循环代码块一定的次数

一个for循环会一直重复执行,直到指定的循环条件为fasle。 JavaScript的for循环和Java与C的for循环是很相似的。一个for语句是这个样子的:

1
2
for ([initialExpression]; [condition]; [incrementExpression])
statement

使用示例

1
2
3
for (var i = 0; i < options.length; i++) {
statement
}

break

终止一个 for 循环

continue

跳过余下代码逻辑,直接进入下一个循环。


🥈 While / do…while

一个 while 语句只要指定的条件求值为真(true)就会一直执行它的语句块。一个 while 语句看起来像这样:

1
2
while (condition)
statement

break

终止一个 while 循环

continue

跳过余下代码逻辑,直接进入下一个循环。


🥈 forEach / 数组遍历

forEach 是 ES5 中加入的新的函数,对数组的每个元素执行一次。

1
2
3
array.forEach(callback(currentValue, index, array){
//do something
}, this)

优点是语法简洁;

缺点是不支持使用 break 来中断,也不能 return 进行返回。

配合箭头函数

1
array.forEach(n => console.log(n));

async

forEach 是不支持 async function 的。Polyfill

但可以创建自己的 asyncForEach 方法来方便使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// define
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array)
}
}
// using
const waitFor = (ms) => new Promise(r => setTimeout(r, ms))
const start = async () => {
await asyncForEach([1, 2, 3], async (num) => {
await waitFor(50)
console.log(num)
})
console.log('Done')
}
start()


🥈 for…in / 遍历对象的属性

这个 for…in 语句循环一个指定的变量来循环一个对象所有可枚举的属性。JavaScript 会为每一个不同的属性执行指定的语句。

1
2
3
for (variable in object) {
statements
}

for-in 是为普通对象设计的,可以遍历得到字符串类型的键,因此不适用于数组遍历。

  • 数组的 for-in 循环体除了遍历数组元素外,还会遍历自定义属性
  • 一些情况下,for…in 代码可能按照随机顺序遍历数组元素


🥈 for…of / 全新迭代器循环

for...of 是 ECMA2015 中新加入的,用于可迭代对象上创建循环。

像常见的 Array, Map, Set,String, Collection, 有enumerable属性的对象, generators 等。

缺点

没有 index,要自己实现。

语法

1
2
3
for (variable of object) {
statement
}

示例(与 for…in 的区别)

下面的这个例子展示了 for…of 和 for…in 两种循环语句之间的区别。与 for…in 循环遍历的结果是数组元素的下标不同的是, for…of 遍历的结果是元素的值:

1
2
3
4
5
6
7
8
9
10
let arr = [3, 5, 7];
arr.foo = "hello";
for (let i in arr) {
console.log(i); // logs "0", "1", "2", "foo"
}
for (let i of arr) {
console.log(i); // logs "3", "5", "7" // 注意这里没有 hello
}

async

for…of 是支持异步使用的,这也是我们常用来进行 async 等待的方法。

1
2
3
4
5
let page = 0;
for (let i of array) {
const products = await fetchProducts(conn, page)
page++;
}


🥈 map

map reduce 是新的高阶函数。它遍历数组中的每一个元素,对期进行一些操作,然后返回一个新的数组。

配合箭头函数

1
array.map(n => console.log(n));


REF::


HTTP Authentication

今天我们来学习一个古老的话题,认证,这可是随着网络出现就有的一个东西,它的主要功能就是知道你是谁。

HTTP 提供了一套标准的身份验证框架:服务器可以用来针对客户端的请求发送质询 (challenge),客户端根据质询提供身份验证凭证。质询与应答的工作流程如下:服务器端向客户端返回 401(Unauthorized,未授权)状态码,并在 WWW-Authenticate 头中添加如何进行验证的信息,其中至少包含有一种质询方式。然后客户端可以在请求中添加Authorization 头进行验证,其 Value 为身份验证的凭证信息。

HTTP 协议提供了一种通用的框架来处理认证和访问控制,像从最早时的 HTTP 1.0 协议 RFC 1945Section 11.1 中提出的 Basic Auth, 到后面更新的 HTTP/1.1: Authentication

MDN 上有一个介绍的专题

网络变的越来越复杂,认证的方式也在不断的升级,在当下,由 OAuth 所提出的 Bearer Authorization 成为了主流。


⭐️ ️️理解 Authentication

由于 HTTP 是无状态的,对于每一个请求,你是谁就是一个问题,所以需要在每一个请求体上附带一个信息,这个信息的作用就是我是谁。

HTTP Authentication 协议约定在 Header 中加入一个条目,然后约定 Key 和 Value 的格式。

我们把 Key 称为 Request Header Field,而 Value 中的格式又是千变万化的,为此,又有不同的标准出现,于是就有了 Authorization Scheme 来进行区分,而 Value 的格式则为

1
scheme auth-token

其中 Scheme 常见的有 Basic, Bearer, OAuth 及其它标准。


Request Header Field:

  • Authenticate
  • WWW-Authenticate
  • Proxy-Authorization
  • Proxy-Authenticate

什么区别:

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization

Authorization:


⭐️ ️️HTTP Authorization Scheme

HTTP Authentication Scheme Registry

在HTTP标准验证方案中,我们比较熟悉的是 “Basic” 和 “Digest”,前者将用户名密码使用BASE64编码后作为验证凭证,后者是Basic的升级版,更加安全,因为Basic是明文传输密码信息,而Digest是加密后传输。在前文介绍的Cookie认证属于Form认证,并不属于HTTP标准验证。

The general HTTP authentication framework is used by several authentication schemes. Schemes can differ in security strength and in their availability in client or server software.

The most common authentication scheme is the “Basic” authentication scheme which is introduced in more details below. IANA maintains a list of authentication schemes, but there are other schemes offered by host services, such as Amazon AWS. Common authentication schemes include:

🔅 几种常见的 Scheme

这几种的区别。

https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/understanding-http-authentication


🔅 Bearer Authentication

Bearer 认证,只有一点,后面跟的是一个字符串。就像 前面 Basic,也是一个字符串,但它是 username:password b64token 。这里可能只要求是一个字符串,即不关心你是 base64,也不关心有没有意义吧。。。

Bearer 认证

本文要介绍的 Bearer验证 也属于HTTP协议标准验证,它随着OAuth协议而开始流行,详细定义见: RFC 6570。
October 2012

以下这段文字,让我理解出 Authentication 和 Token 两个概念。有了这两个层次的区分。才理解我的问题在哪里。

Bearer authentication (also called token authentication) is an HTTP authentication scheme that involves security tokens called bearer tokens. The name “Bearer authentication” can be understood as “give access to the bearer of this token.”

The bearer token is a cryptic string, usually generated by the server in response to a login request. The client must send this token in the Authorization header when making requests to protected resources:

1
Authorization: Bearer <token>

The Bearer authentication scheme was originally created as part of OAuth 2.0 in RFC 6750, but is sometimes also used on its own.

但有时也会单独使用。

Similarly to Basic authentication, Bearer authentication should only be used over HTTPS (SSL).

你看 OpenId 中也有用到 Bearer 的概念。。
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#jwt-bearer-sample

好处

那么使用 Bearer 验证有什么好处呢?

  • CORS: cookies + CORS 并不能跨不同的域名。而Bearer验证在任何域名下都可以使用HTTP header头部来传输用户信息。
  • 对移动端友好: 当你在一个原生平台(iOS, Android, WindowsPhone等)时,使用Cookie验证并不是一个好主意,因为你得和Cookie容器打交道,而使用Bearer验证则简单的多。
  • CSRF: 因为Bearer验证不再依赖于cookies, 也就避免了跨站请求攻击。
  • 标准:在Cookie认证中,用户未登录时,返回一个302到登录页面,这在非浏览器情况下很难处理,而Bearer验证则返回的是标准的401 challenge。


🔅 Basic Authentication Scheme

Basic Auth 是最古老的一种认证协议,在 HTTP 1.0 中,就定认了认证的协议:

最初的 RFC 在 HTTP 1.0 里。
https://tools.ietf.org/html/rfc1945#section-11.1

The ‘Basic’ HTTP Authentication Scheme
https://tools.ietf.org/html/rfc7617

Basic authentication is a simple authentication scheme built into the HTTP protocol. The client sends HTTP requests with the Authorization header that contains the word Basic word followed by a space and a base64-encoded string username:password. For example, to authorize as demo / p@55w0rd the client would send:

就是用 户名:密码,b64 一下:

1
b64(zhiming:123465) = emhpbWluZzoxMjM0NjU=

use the following header field:

1
Authorization: Basic emhpbWluZzoxMjM0NjU=

https://security.stackexchange.com/questions/108662/why-is-bearer-required-before-the-token-in-authorization-header-in-a-http-re

twitter basic

这是 Twitter 的开发者中心找到的示例:

虽然不知道还有什么情景是需要这种协议的。

1
2
curl -v --compressed -u<email_address>:<password>
"https://gnip-api.twitter.com/search/30day/accounts/<ACCOUNT-NAME>/prod/counts.json?query=from%3Atwitterdev"


🔅 其它

- Digest

后来 Firefox 对此进行了升级。这里是明文。升级为。。。密码型

- HOBA Authentication Scheme

A new scheme has been registered in the HTTP Authentication Scheme Registry as follows:

1
Authentication Scheme Name: HOBA

- HAWK Authentication Scheme

1
Authentication Hawk id="131", ts="32", nonce="243", ext="243", mac="mqWeAM8ZP6Dm+7vBBh0qxUKZftLC4iFmYjRnvJfcSAc="

- OAuth Authentication Scheme

在 OAuth 1 的版本中,定义了 OAuth 的 Auth scheme,目前 Twitter 依然在严格采用。


⭐️ ️️Bearer Tokens 种类

下面是一段很不错的介绍,不把它和 OAuth 联系在一起看,很能说明 Bearer Token 的定义,用途。

A security token with the property that any party in possession of the token (a “bearer”) can use the token in any way that any other party in possession of it can. Using a bearer token does not require a bearer to prove possession of cryptographic key material (proof-of-possession).

The Bearer Token or Refresh token is created for you by the Authentication server. When a user authenticates your application (client) the authentication server then goes and generates for you a Bearer Token (refresh token) which you can then use to get an Access Token.

The Bearer Token is normally some kind of secret value created by the authentication server. It isn’t random; it is created based upon the user giving you access and the client your application getting access.

In order to access an API for example you need to use an Access Token. Access tokens are short lived (around an hour). You use the bearer token to get a new Access token. To get an access token you send the Authentication server this bearer token along with your client id. This way the server knows that the application using the bearer token is the same application that the bearer token was created for. Example: I can’t just take a bearer token created for your application and use it with my application it wont work because it wasn’t generated for me.

Google Refresh token looks something like this: 1/mZ1edKKACtPAb7zGlwSzvs72PvhAbGmB8K1ZrGxpcNM

copied from comment: I don’t think there are any restrictions on the bearer tokens you supply. Only thing I can think of is that its nice to allow more then one. For example a user can authenticate the application up to 30 times and the old bearer tokens will still work. oh and if one hasn’t been used for say 6 months I would remove it from your system. It’s your authentication server that will have to generate them and validate them so how it’s formatted is up to you.

关于 bear token,参看 RFC 6750: The OAuth 2.0 Authorization Framework: Bearer Token Usage , 目前国内各大网站都是用不同的token,也没说必须使用bear token,只有twitter明确说明的是使用 bear token。

OAuth 2.0 (RFC 6749) 定义了 Client 如何取得 Access Token 的方法。Client 可以用 Access Token 以 Resource Owner 的名义来向 Resource Server 取得 Protected Resource ,例如我 (Resource Owner) 授权一個手机 App (Client) 以我 (Resource Owner) 的名义去 Facebook (Resource Server) 取得我的朋友名单 (Protected Resource)。OAuth 2.0 定义Access Token 是 Resource Server 用来认证的唯一方式,有了这个, Resource Server 就不需要再提供其他认证方式,例如账号密码。

然而在 RFC 6749 里面只定义抽象的概念,细节如 Access Token 格式、怎么传到 Resource Server ,以及 Access Token 无效时, Resource Server 怎么处理,都没有定义。所以在 RFC 6750 另外定义了 Bearer Token 的用法。Bearer Token 是一种 Access Token ,由 Authorization Server 在 Resource Owner 的允许下核发给 Client ,Resource Server 只要认在这个 Token 就可以认定 Client 已经获取 Resource Owner 的许可,不需要用密码学的方式来验证这个 Token 的真伪。关于Token 被偷走的安全性问题,另一篇再说。

Bearer Token 的格式

1
Bearer <token>

其中 XXXXXXXX 的格式 b64token ,ABNF 的定義:

1
b64token = 1*( ALPHA / DIGIT / “-” / “.” / “_” / “~” / “+” / “/” ) *”=”

写成 Regular Expression 即是:

1
/[A-Za-z0-9/-/._~/+//]+=*/

Bearer验证中的凭证称为 BEARER_TOKEN,或者是 access_token,它的颁发和验证完全由我们自己的应用程序来控制,而不依赖于系统和Web服务器,Bearer 验证的标准请求方式如下:

1
Authorization: Bearer <BEARER_TOKEN>

那 weibo, facebook, wechat 签发出来的 token 用了什么标准了吗?

custom

首先,大家是没有一个标准的。每个大厂在这一块,都是乱来的。

JWT(JSON WEB TOKEN)

上面介绍的Bearer认证,其核心便是BEARER_TOKEN,而最流行的Token编码方式便是:JSON WEB TOKEN。

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519)。该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。
JWT 的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

JSON Web Token 是如何工作的?

在身份验证中,当用户成功地使用它们的密码登录后,一个JSON Web Token就会被服务器返回,且必须存储于客户端本地(通常存于localStorage,也可以使用cookie存储),而不是传统做法中在服务器创建一个session,并返回一个cookie。

无论何时用户想要访问一个受保护的路由或资源,客户端应该随请求一起发送JWT。JWT通常在 Authentication HTTP头部中,使用 Bearer 格式(schema)存放。HTTP头部的内容可能长这样:

1
Authorization: Bearer <token>

这是一个无状态(stateless)的认证机制,因为用户状态并未存放在服务器内存中。服务器的受保护路由会在Authentication头部中检查是否存在合法的JWT,如果存在,用户就被允许访问受保护的资源。JWT是自包含的,所以相关的信息都在里面,减少了多次查询数据库的必要。

这让你充分可以依赖无状态的数据API,甚至向下游服务发出请求。哪个域名提供API无所谓,所以跨域资源共享(CORS,Cross-Origin Resource Sharing)不会引起麻烦,因为JWT不使用cookie。

为啥不是???

1
Authorization: JWT <token>

可以参见这些
https://swagger.io/docs/specification/authentication/bearer-authentication/

Bearer 定义的是更高级的

Bearer token formatter 有很多形式:

  • JWT
  • Plain???? 之前的 oauth access_token

JWT 对数据进行了安全校验。防止被篡改。
可是像一个 access_token,什么时间会被篡改呢?

https://github.com/smilingsun/blog/issues/1

SWT (Simple Web Token)

SAML 2.0 (Security Assertion Markup Language)

https://tools.ietf.org/html/rfc7522


⭐️ 参考

以下是 Twitter 的 OAuth 使用详情:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
HTTP POAT
https://api.twitter.com/1.1/saved_searches/list.json
Authorization OAuth oauth_consumer_key="kxBVHz34SYKxZmdV3tjbA",oauth_token="51027998-aX4ggPDBL1KbcbrfaFpHP1WCc0rwtpDsJHXHEl8UK",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1450240474",oauth_nonce="wg3OPf",oauth_version="1.0",oauth_signature="bAbflN%2F8iyRscuagVqzNjJnDqws%3D"
POST /1.1/statuses/update.json?include_entities=true HTTP/1.1
Accept: */*
Connection: close
User-Agent: OAuth gem v0.4.4
Content-Type: application/x-www-form-urlencoded
Authorization:
OAuth oauth_consumer_key="xvz1evFS4wEEPTGEFPHBog",
oauth_nonce="kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg",
oauth_signature="tnnArxj06cWHq44gCs1OSKk%2FjLY%3D",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1318622958",
oauth_token="370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb",
oauth_version="1.0"
Content-Length: 76
Host: api.twitter.com
status=Hello%20Ladies%20%2b%20Gentlemen%2c%20a%20signed%20OAuth%20request%21

Github

1
2
3
4
5
6
7
8
# Basic authentication
curl -u "username" https://api.github.com
# OAuth2 token (sent in a header)
curl -H "Authorization: token OAUTH-TOKEN" https://api.github.com
# OAuth2 token (sent as a parameter)
curl https://api.github.com/?access_token=OAUTH-TOKEN

weibo

1
2
3
4
5
6
7
https://api.weibo.com/oauth2/access_token?client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&grant_type=authorization_code&redirect_uri=YOUR_REGISTERED_REDIRECT_URI&code=CODE
{
"access_token": "SlAV32hkKG",
"remind_in": 3600,
"expires_in": 3600
}

接下来所有的东西都走 access_token 了。

wechat

1
2
3
4
5
http://open.wechat.com/cgi-bin/newreadtemplate?t=overseas_open/docs/mobile/login/token#login_token
HTTP request: GET
https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID

jira

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
https://developer.atlassian.com/cloud/jira/software/oauth-2-jwt-bearer-token-authorization-grant-type/
POST /oauth2/token HTTP/1.1
Host: auth.atlassian.io
Accept: application/json
Content-Length: {length of the request body}
Content-Type: application/x-www-form-urlencoded
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&scope=READ+WRITE&assertion={your-signed-jwt}
HTTP/1.1 200 OK
Status: 200 OK
Content-Type: application/json; charset=utf-8
...
{
"access_token": "{your access token}",
"expires_in": {15 minutes expressed as seconds},
"token_type": "Bearer"
}

Using an access token in a request

Set the Authorization header to Bearer {your access token} and make your request:

1
2
3
4
5
GET /rest/api/latest/myself HTTP/1.1
Host: {your-registered-instance}.atlassian.net
Accept: application/json
Authorization: Bearer {your-access-token}

https://help.salesforce.com/articleView?id=remoteaccess_oauth_jwt_flow.htm&type=5


⭐️ ️️我的QA

🔅 OAuth 2 和 scheme

https://tools.ietf.org/html/rfc6749#section-2.3

可以看到,OAuth 2 在 Client Authentication 上,是不限定哪种 scheme 的,它在 core rfc 上,只是提到了 Basic。

于是它后来又专门出了个 RFC : 6750,来写 bearer

2.3.2. Other Authentication Methods

The authorization server MAY support any suitable HTTP authentication scheme matching its security requirements. When using other authentication methods, the authorization server MUST define a mapping between the client identifier (registration record) and authentication scheme.

Bearer 可以做到 和 OAuth 没有关系吗?

就是我自己写个验证,也用 Bearer?或者说有多个 Auth 是用 Bearer 的???

我现在的理解就是 虽然 Bearer是在 OAuth 中提出的,但也可以用在其它的验证框架中。
它只是个关键字。如何解析它,需要框架的支持。而 OAuth 就可能支持。

Bearer token 可以由多少种方式构成。

可多了。像 SWT/JWT。或者是自定义

有多少用 Bearer Scheme 的?

几大网站,没有一个!!!

为什么??

⭐️ ️️REF

https://auth0.com/blog/ten-things-you-should-know-about-tokens-and-cookies/

JSON Web Token - 深入

在上一篇,我们介绍了 Web Token 的概念,也提到了 JSON Web Token。这里,再来对 JWT 进行深入的研究,看看在实际应用中的场景。

JWT 目前的场景都是用来记录用户登录信息数据的。这很容易让人想起近些年各大平台所采用的 OAuth 2.0,这也是用来进行登录授权的,它们有什么关系和区别?


⭐️ ️️OAuth 2.0 - Tokens

OAuth 启源于平台希望共享自己的内容给第三方,和第三方共同成长。但 OAuth 协议本身并未对 token 进行约定。

可以说 JWT 和 OAuth 协议本身并不在一个层面上,JWT 负责数据传输中的标准格式,而 OAuth 则负责签发证书和负责证书有效时长等。

但 OAuth 使用者比较要传递 token 啊,token 长什么样,如何定义?

这就有了 Bearer Token,JWT 实际上是在和它进行对比。

🔅 OAuth Bearer Token 举例

比如说这是一个 password 类型的 grant_type

1
2
3
grant_type=password&client_id=3MVG9lKcPoNINVBIPJjdw1J9LLM82Hn
FVVX19KY1uA5mu0QqEWhqKpoW3svG3XHrXDiCQjK1mdgAvhCscA9GE&client_secret=
1955279925675241571&username=testuser%40salesforce.com&password=mypassword

这是它的返回值:

1
2
3
4
5
6
7
8
{
"id":"https://login.salesforce.com/id/00Dx0000000BV7z/005x00000012Q9P",
"issued_at":"1278448832702”,
"instance_url":"https://yourInstance.salesforce.com/",
"signature":"0CmxinZir53Yex7nE0TD+zMpvIWYGb/bdJh6XfOH6EQ=“,
"access_token”: "00Dx0000000BV7z!AR8AQAxo9UfVkh8AlV0Gomt9Czx9LjHnSSpwBMmbRcgKFmxOtvxjTrKW19ye6PE3Ds1eQz3z8jr3W7_VbWmEu4Q8TVGSTHxs”,
"token_type":”Bearer"
}

注意到,返回值中有 access_token 和 token_type=”Bearer”

🔅 常见 OAuth Token 方案:

其中的 Bearer 就是 OAuth 扩展协议 RF6750 加入的对 Token 具体的约定,当然了,结合上两讲的 Web Token,这里还会有其它类型的方案,像 RFC7523 的 JWT Profile 和 RFC7522 的 SAML2.0.

🔅 OAuth 2.0 - Bearer token

OAuth 2 里,其实并未说明 token 的 DSF,后来才加入的 Bearer Token

https://tools.ietf.org/html/rfc6750

Bearer Token 的格式 JWTs 传递 Toke 就是利用新定义的

1
Bearer XXXXXXXX

其中 XXXXXXXX 的格式為 b64token ,ABNF 的定義:

1
b64token = 1*( ALPHA / DIGIT / "-" / "." / "_" / "~" / "+" / "/" ) *"="

寫成 Regular Expression 即是:

1
/[A-Za-z0-9\-\._~\+\/]+=*/

Authorization: Bearer

🔅 OAuth 2.0 - SAML Bearer Token

🔅 OAuth 2.0 - JWT Bearer Token

JSON Web Token (JWT):RFC7519 - JSON Web Token (JWT),该协议定义了一种自包含Token类型及完整性校验方法;RFC7797 - JSON Web Signature (JWS) Unencoded Payload Option,该协议对RFC7519的更新

JWT 是 access_token 的格式的一种实现。

OAuth 2.0 用户名密码流程


⭐️ ️️微服务下的 JWT

OAuth2 负责签发 Token,而 Edge Service 做自己的 JWT,在 JWT 的 payload 中放入自己的数据。

当然 OAuth provider 也可以用 JWT,如果他们需要带上一些附属信息的话,而 Edge Service 可以根据自己的情况,做自己的 JWT。

JSON Web Token - 入门

在上一篇,我们介绍了 Web Token 的概念,也提到了 JSON Web Token。这里,再来对 JWT 进行深入的研究。


⭐️ ️️JWT 介绍

JSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is digitally signed using JSON Web Signature (JWS).

特点

  • 体积小,因而传输速度快
  • 传输方式多样,可以通过URL/POST参数/HTTP头部等方式传输
  • 严格的结构化。它自身(在 payload 中)就包含了所有与用户相关的验证消息,如用户可访问路由、访问有效期等信息,服务器无需再去连接数据库验证信息的有效性,并且 payload 支持为你的应用而定制化。
  • 支持跨域验证,可以应用于单点登录。


⭐️ ️️JWT 格式

JWT 使用了 JSON 格式,由三个部分组成:

  • Header
  • Payload
  • Signature

看起来像这样:

1
xxxxx.yyyyy.zzzzz

🔅 Header

头部由两部分信息:

  • 声明类型,这里是 JWT
  • 声明加密的算法 通常直接使用 HMAC SHA256/RSA
1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

将这部分值进行 Base64 得到 JWT 第一部分: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

🔅 Payload

Payload 是存放信息的地方,它有也一些标准字段的定义,但都不是强制的。

  • 标准中注册的声明
  • 公共的声明
  • 私有的声明

标准中注册的声明

JWT 标准所定义了五个值

  • iss: 该JWT的签发者
  • sub: 该JWT所面向的用户
  • aud: 接收该JWT的一方
  • exp(expires): 什么时候过期,这里是一个Unix时间戳
  • iat(issued at): 在什么时候签发的
  • nbf: 定义在什么时间之前,该jwt都是不可用的
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击

这里我们不使用标准,自行放一些数据:

1
2
3
4
5
{
"id": "1234567890",
"name": "Zhiming Jiang",
"handsome": true
}

最后将这部分值进行 Base64 得到 JWT 第二部分: eyJpZCI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoiWmhpbWluZyBKaWFuZyIsImhhbmRzb21lIjp0cnVlfQ

🔅 Signature

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64后的)
  • payload (base64后的)
  • secret

它由前两部分的 Base64 值合在一起,加上一个 salt 什值,再由 Header 中的加密方式对这个字符串进行加密生成这一部分: yHFP8ABW2Oxk3eH1C8ODyPf6UO84yP7zbXOaVY5boUE

🔅 最终

1
base64(Header) + '.' + base64(Claims) + '.' + base64(Signature)

最终,就变成这样的一个字符串:

很小,这方便了它的传输,可以放在 Header 里,也可以放在 QueryString 中。


⭐️ ️️应用

一般是在请求头里加入Authorization,并加上Bearer标注:

1
2
3
headers: {
'Authorization': 'Bearer ' + token
}

服务端会验证token,如果验证通过就会返回相应的资源。整个流程就是这样的:


⭐️ ️️总结

JSON 现在是事实的 DSF 标准了,所有的主流语言都支持 序列化/反序列化,所以使用 JSON 是一个不错的选择。

Payload 部分可以自由存储,方便扩展。

JWT 相对比较小巧,便于传输。

保证了数据不会被篡改,对安全性有保证

安全

依然要使用 HTTPS 协议来进行安全保证

Payload 部分不应该放敏感信息。

不要泄露 Secret 信息。

Web Token

最近在一个新项目中,要做鉴权的事宜,于是 Token 的话题又被拿了出来,JWT 是一个很火的名字,但事实上,并没有人很了解这个东西,于是我准备自己来解释下。


⭐️ ️️如何传递用户的安全信息

HTTP 的无状态特性

在 Web 应用中,经常要处理的一类问题是,验证。如何记住当前是哪一个用户是一件很麻烦的事情。

这是因为 HTTP 协议是短连接,每次请求都是无状态的,这使得每次客户端和服务器都需要在会话时,带上 我是谁 这个信息。

用户安全信息常用有两种方式:

  • Cookie
  • Token

HTTP 协议 中有关于 Cookie 的定义。人们可以通过将数据写入 cookie ,每次 HTTP 请求都带上这段信息的方式来进行信息传递。

用户安全信息不属于业务的部分,被放在 Cookie 里再适合不过了。

通常,在 Cookie 里记录着用户的安全信息,这些信息包括 token,或者一些用户的数据,以减少服务端每次查询的压力。

但浏览器为了安全考虑,通常对 Cookie 做了严格的限制,像不得跨域携带等,在 API 时代,Cookie 更加的不方便使用。

🔅 HTTP Request

于是,一个解题的新思路就来了,能不能把数据放在 HTTP 的请求头中,像 Header 或 QueryString 中。

我们把这种方式叫做 Web Token。

好处显而易见,不再有跨域的麻烦,而且是 HTTP 标准,处理起来也很方便。


⭐️ Web Tokens

我们定义了 Web Token 在请求体中的位置,但 Web Token 的格式,还没有做要求。理论上一个请求体上的内容,如果仅是自己在用的话,怎么写都可以,但当各方站点需要一起通讯的话,或者为了方便交流的原因,也会形成一个共用标准的。

于是就有以下几种常见标准:

  • JSON Web Tokens (JWT)
  • Simple Web Tokens (SWT)
  • Security Assertion Markup Language Tokens (SAML)

对比。

SWT 定义了文本的格式,有几个字段的标准,最终生成的是这些字符串的 base64
而 SAML 则是使用 XML 格式
JWT 则使用 JSON,除此之外,JWT 还定义了一些字段的要求

🔅 SWT

SWT 只在 MSDN 和 JWT 的说明文档上见到,可见使用不是特别的广泛。

这是它的定义部分:

1
2
3
4
Issuer = issuer.example.com
ExpiresOn = 1/1/2010, Midnight
com.example.group = gold
over18 = true

Base64 后的结果

1
N4QeKa3c062VBjnVK6fb+rnwURkcwGXh7EoNK34n0uM=

🔅 SAML 2.0

除了 XML 数据格式外,还定义了以下标准字段:

  • saml:Issuer
  • ds:Signature
  • saml:Subject
  • saml:Conditions
  • saml:AuthnStatement
  • saml:AttributeStatement

可以看下 Wiki 上的定义内容,特点就是冗长,因为在长了,这里也就不再复制了。

🔅 JWT

JWT 是目前最火的 Web Token 协议,已经被加入 RFC 中了。这得益于 JSON 的优点,广泛的平台和比较轻巧的格式。

简单的说,它使用了 JSON 格式,由三个部分组成:

  • Header
  • Payload
  • Signature

头部由两部分信息:

  • 声明类型,这里是 JWT
  • 声明加密的算法 通常直接使用 HMAC/SHA256
1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

将这部分值进行 Base64 得到 JWT 第一部分: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

Payload

Payload 是存放信息的地方,它有也一些标准字段的定义,但都不是强制的。

1
2
3
4
5
{
"id": "1234567890",
"name": "John Doe",
"admin": true
}

最后将这部分值进行 Base64 得到 JWT 第二部分:eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

Signature

它由前两部分的 Base64 值合在一起,再由 Header 中的加密方式生成这一部分:TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

最终,就变成这样的一个字符串:

很小,这方便了它的传输,可以放在 Header 里,也可以放在 QueryString 中。


⭐️ 后续

这里我们介绍了如何传递用户安全数据的方式,然后对 Web Token 的几种常见标准进行了介绍。

作为主流当红的 JWT,接下来我们会再花笔墨去重点介绍下。

pyenv in Action

最近在做 AI 相关的工作,Python 作为科学第一语言又被拾起,不过 macOS 中用的依然是 2.7,十年前曾因这个版本放弃 Python,好在现在 Python3 已经比较普及了,如今自然也不会用它。

使用 pyenv 可以安装多个python版本,并通过命令切换当前使用哪个版本。


安装 pyenv

1
2
3
$ xcode-select --install
$ brew install openssl readline xz

macOS 安装 - Homebrew

在 macOS 上,更推荐使用 Homebrew 来进行安装,这样,软件本身的版本升级就变得可以解决了。而 pyenv 就只专注于 python 的版本控制。

- 安装

1
brew install pyenv

- 配置 .zshrc

1
2
3
4
5
6
# pyenv for Python
# ===================
if command -v pyenv 1>/dev/null 2>&1; then
eval "$(pyenv init -)"
fi

- 更新

1
2
brew update
brew upgrade pyenv

- 删除

1
brew uninstall pyenv

手工安装 - git

1
git clone https://github.com/pyenv/pyenv.git ~/.pyenv

配置 .zshrc

git 安装,还需要对下载的 pyenv 进行环境的配置。

1
2
3
4
5
6
7
8
9
# pyenv for Python
# ===================
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
if command -v pyenv 1>/dev/null 2>&1; then
  eval "$(pyenv init -)"
fi

删除 pyenv

卸载很简单,直接删除 ~/.pyenv 目录即可。也要去掉 .zshrc 加入的语句。


安装使用 python

查看可安装的版本

1
$ pyenv install --list

安装 python3 当前最新版本 3.6.4:

1
2
3
4
5
6
7
$ pyenv install 3.6.4
python-build: use openssl from homebrew
python-build: use readline from homebrew
Installing Python-3.6.4...
python-build: use readline from homebrew
Installed Python-3.6.4 to /Users/lanvige/.pyenv/versions/3.6.4

设置为全局版本

1
2
3
$ pyenv global 3.6.4
$ python -V

全局还是用系统自带的,可以这样设置:

1
$ pyenv global system

当前目录下使用python 3.6.4

1
$ pyenv local 3.6.4

###


使用 pip

pip 是 Python的 包管理工具,安装完成后,就可以使用 pip 来进行包的安装了。

包的查找,可以通过这里进行: https://pypi.python.org/pypi

选择正确的 Python 版本:

1
2
3
$ python3 --version
# Python 3.6.3

安装 Tensorflow

1
$ pip install tensorflow

升级 TensorFlow

1
$ pip install --upgrade tensorflow

该操作会自动删除旧的库的版本。


国内加速

  1. pyenv搜狐镜像源加速:http://mirrors.sohu.com/python/
  2. 下载需要的版本放到~/.pyenv/cache文件夹下面
  3. 然后执行 pyenv install 版本号 安装对应的python版本
  4. 傻瓜式脚本如下,其中v表示要下载的版本号
1
v=3.6.4|wget [http://mirrors.sohu.com/python/$v/Python-$v.tar.xz](http://mirrors.sohu.com/python/$v/Python-$v.tar.xz) -P ~/.pyenv/cache/;pyenv install $v


QA

安装时遇到 “zlib not available”

1
xcode-selectinstall


REF::


Kubernetes - Init Cluster on CentOS

🎖 准备

🔅 配置防火墙(firewalld)

1
2
3
4
5
# 临时关闭防火墙
$ systemctl stop firewalld
# 永久防火墙开机自启动
$ systemctl disable firewalld

🔅 关闭 Swap

Kubernetes 1.8 开始要求关闭系统的 Swap,如果不关闭,默认配置下kubelet将无法启动。可以通过 kubelet 的启动参数 --fail-swap-on=false 更改这个限制。 我们这里关闭系统的 Swap:

1
$ swapoff -a

关闭之前:

1
2
3
4
$ free -m
total used free shared buff/cache available
Mem: 992 113 636 6 242 716
Swap: 2047 0 2047

关闭之后:

1
2
3
4
$ free -m
total used free shared buff/cache available
Mem: 992 111 638 6 242 718
Swap: 0 0 0

🔅 禁用 SELINUX:

1
$ setenforce 0

确认:

1
2
3
$ vi /etc/selinux/config
SELINUX=disabled

🔅 安装 Docker

Docker 的安装比较复杂,在另一篇文章中有单独讲安装和配置。

这里安装的是 docker-ce-17.10

这里,因为 kubernetes 用的 cgroup driver 为 systemd,所以要把 docker 也配置为 systemd。通过 info 命令确认。

1
2
3
4
5
6
7
8
9
# docker info
Server Version: 17.09.0-ce
Storage Driver: overlay
Backing Filesystem: xfs
Supports d_type: true
Logging Driver: json-file
Cgroup Driver: systemd
...


🎖 安装

🔅 更新源

1
2
3
4
5
6
7
8
9
10
11
12
13
# 如果需要 sudo 用户,请
$ su
$ cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg
https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF

🔅 更新

1
$ sudo yum makecache fast

🔅 安装 kubernets 主要软件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ sudo yum install -y kubelet kubeadm kubectl
...
Dependencies Resolved
======================================================================================================================================================
Package Arch Version Repository Size
======================================================================================================================================================
Installing:
kubeadm x86_64 1.8.1-0 kubernetes 15 M
kubectl x86_64 1.8.1-0 kubernetes 7.3 M
kubelet x86_64 1.8.1-0 kubernetes 16 M
Installing for dependencies:
kubernetes-cni x86_64 0.5.1-1 kubernetes 7.4 M
socat x86_64 1.7.3.2-2.el7 base 290 k
Transaction Summary
======================================================================================================================================================

🔅 版本确认

1
2
3
$ kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"8", GitVersion:"v1.8.1", GitCommit:"f38e43b221d08850172a9a4ea785a86a3ffa3b3a", GitTreeState:"clean", BuildDate:"2017-10-11T23:16:41Z", GoVersion:"go1.8.3", Compiler:"gc", Platform:"linux/amd64”}


🎖 配置 Master与问题集

1
$ sudo kubeadm init --kubernetes-version=v1.8.1 --pod-network-cidr=10.244.0.0/16

正常来说,这条命令就会启动一个 Master 了,但实际情况是新的 Kubernetes 是使用容器来启动三方软件的,而这些容器放置在 gcr.io,需要翻墙,这里我们预加载一下。

系统启动一共需要以下镜像:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
gcr.io/google_containers/kube-apiserver-amd64:v1.8.1
gcr.io/google_containers/kube-controller-manager-amd64:v1.8.1
gcr.io/google_containers/kube-scheduler-amd64:v1.8.1
gcr.io/google_containers/pause-amd64:3.0
gcr.io/google_containers/etcd-amd64:3.0.17
gcr.io/google_containers/kube-proxy-amd64:v1.8.1
gcr.io/google_containers/k8s-dns-dnsmasq-nanny-amd64:1.14.5
gcr.io/google_containers/k8s-dns-sidecar-amd64:1.14.5
gcr.io/google_containers/k8s-dns-kube-dns-amd64:1.14.5
quay.io/coreos/flannel-amd64:v0.9.0

🔅 重新开始

1
2
3
$ kubeadm reset
$ sudo kubeadm init --kubernetes-version=v1.8.1 --pod-network-cidr=10.244.0.0/16

🔅 完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
$ kubeadm init --kubernetes-version=v1.8.1 --pod-network-cidr=10.244.0.0/16
# 以下为输出
[kubeadm] WARNING: kubeadm is in beta, please do not use it for production clusters.
[init] Using Kubernetes version: v1.8.1
[init] Using Authorization modes: [Node RBAC]
[preflight] Running pre-flight checks
[preflight] WARNING: docker version is greater than the most recently validated version. Docker version: 17.09.0-ce. Max validated version: 17.03
[preflight] Starting the kubelet service
[kubeadm] WARNING: starting in 1.8, tokens expire after 24 hours by default (if you require a non-expiring token use --token-ttl 0)
[certificates] Generated ca certificate and key.
[certificates] Generated apiserver certificate and key.
[certificates] apiserver serving cert is signed for DNS names [localhost.localdomain kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 192.168.8.78]
[certificates] Generated apiserver-kubelet-client certificate and key.
[certificates] Generated sa key and public key.
[certificates] Generated front-proxy-ca certificate and key.
[certificates] Generated front-proxy-client certificate and key.
[certificates] Valid certificates and keys now exist in "/etc/kubernetes/pki"
[kubeconfig] Wrote KubeConfig file to disk: "admin.conf"
[kubeconfig] Wrote KubeConfig file to disk: "kubelet.conf"
[kubeconfig] Wrote KubeConfig file to disk: "controller-manager.conf"
[kubeconfig] Wrote KubeConfig file to disk: "scheduler.conf"
[controlplane] Wrote Static Pod manifest for component kube-apiserver to "/etc/kubernetes/manifests/kube-apiserver.yaml"
[controlplane] Wrote Static Pod manifest for component kube-controller-manager to "/etc/kubernetes/manifests/kube-controller-manager.yaml"
[controlplane] Wrote Static Pod manifest for component kube-scheduler to "/etc/kubernetes/manifests/kube-scheduler.yaml"
[etcd] Wrote Static Pod manifest for a local etcd instance to "/etc/kubernetes/manifests/etcd.yaml"
[init] Waiting for the kubelet to boot up the control plane as Static Pods from directory "/etc/kubernetes/manifests"
[init] This often takes around a minute; or longer if the control plane images have to be pulled.
[apiclient] All control plane components are healthy after 29.502147 seconds
[uploadconfig] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[markmaster] Will mark node localhost.localdomain as master by adding a label and a taint
[markmaster] Master localhost.localdomain tainted and labelled with key/value: node-role.kubernetes.io/master=""
[bootstraptoken] Using token: 405cea.cb350fd16023e08d
[bootstraptoken] Configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstraptoken] Configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstraptoken] Configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstraptoken] Creating the "cluster-info" ConfigMap in the "kube-public" namespace
[addons] Applied essential addon: kube-dns
[addons] Applied essential addon: kube-proxy
Your Kubernetes master has initialized successfully!
To start using your cluster, you need to run (as a regular user):
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
http://kubernetes.io/docs/admin/addons/
You can now join any number of machines by running the following on each node
as root:
kubeadm join --token 405cea.cb350fd16023e08d 192.168.8.78:6443 --discovery-token-ca-cert-hash sha256:87a0229f7837cf1502789e430c57ca381642902f5665005640b917b6fcbf5a3b

按最后的要求,运行这几条命令:

1
2
3
$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config

🔅 结果

1
2
3
4
5
6
7
8
9
10
11
12
13
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
656dbe0d38be gcr.io/google_containers/pause-amd64:3.0 "/pause" 2 minutes ago Up 2 minutes k8s_POD_kube-dns-545bc4bfd4-2wrrg_kube-system_de9494c2-b6f7-11e7-af7d-0800278919d4_0
e0dfea9f00a5 gcr.io/google_containers/pause-amd64:3.0 "/pause" 2 minutes ago Up 2 minutes k8s_POD_kube-proxy-fnj6t_kube-system_de803e34-b6f7-11e7-af7d-0800278919d4_0
da7a7ec9d578 mirrorgooglecontainers/kube-scheduler-amd64 "kube-scheduler --..." 2 minutes ago Up 2 minutes k8s_kube-scheduler_kube-scheduler-localhost.localdomain_kube-system_5fbf3e68ff1f2f57797628887e9c1bec_0
2126fd5a0d57 mirrorgooglecontainers/kube-controller-manager-amd64 "kube-controller-m..." 2 minutes ago Up 2 minutes k8s_kube-controller-manager_kube-controller-manager-localhost.localdomain_kube-system_2f2bd9f6dddf513ac6c21a43335777dd_0
21edd6e71eeb mirrorgooglecontainers/kube-apiserver-amd64 "kube-apiserver --..." 2 minutes ago Up 2 minutes k8s_kube-apiserver_kube-apiserver-localhost.localdomain_kube-system_dc78e47f78457da950a93b36ff6ac4ba_0
8712a0e9833f mirrorgooglecontainers/etcd-amd64 "etcd --listen-cli..." 2 minutes ago Up 2 minutes k8s_etcd_etcd-localhost.localdomain_kube-system_d76e26fba3bf2bfd215eb29011d55250_0
47bd69d72e16 gcr.io/google_containers/pause-amd64:3.0 "/pause" 2 minutes ago Up 2 minutes k8s_POD_kube-controller-manager-localhost.localdomain_kube-system_2f2bd9f6dddf513ac6c21a43335777dd_0
292d54bd936f gcr.io/google_containers/pause-amd64:3.0 "/pause" 2 minutes ago Up 2 minutes k8s_POD_kube-apiserver-localhost.localdomain_kube-system_dc78e47f78457da950a93b36ff6ac4ba_0
dae95c5d2a8f gcr.io/google_containers/pause-amd64:3.0 "/pause" 2 minutes ago Up 2 minutes k8s_POD_kube-scheduler-localhost.localdomain_kube-system_5fbf3e68ff1f2f57797628887e9c1bec_0
5d41a042874c gcr.io/google_containers/pause-amd64:3.0 "/pause" 2 minutes ago Up 2 minutes k8s_POD_etcd-localhost.localdomain_kube-system_d76e26fba3bf2bfd215eb29011d55250_0

🔅 调试

如果在初始化过程中碰到问题,可以通过以下命令来显示日志。

🎖 配置 pod 网络 Flannel

1
2
3
4
5
6
7
8
$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/v0.9.0/Documentation/kube-flannel.yml
clusterrole "flannel" created
clusterrolebinding "flannel" created
serviceaccount "flannel" created
configmap "kube-flannel-cfg" created
daemonset "kube-flannel-ds” created

这条命令运行完之后,是会生成

1
2
3
4
5
6
7
8
9
/etc/cni/net.d/10-flannel.conf
{
"name": "cbr0",
"type": "flannel",
"delegate": {
"isDefaultGateway": true
}
}

这时的 docker 多出现了

1
2
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
aaeef81d5b18 gcr.io/google_containers/pause-amd64:3.0 "/pause" 14 minutes ago Up 14 minutes k8s_POD_kube-flannel-ds-kwhl9_kube-system_5137e57d-b6fd-11e7-af7d-0800278919d4_0


🎖 加入 Worker Node

🔅 更改 docker 为 systemd

🔅 安装 docker

🔅 准备镜像

和 Master 一样,

1
2
3
gcr.io/google_containers/kube-proxy-amd64:v1.8.1
gcr.io/google_containers/pause-amd64:3.0
quay.io/coreos/flannel-amd64:v0.9.0

🔅 安装 kubeadm kubectl 和 cni

1
$ sudo yum install -y kubelet kubeadm

🔅 配置 cni 的插件

1
2
3
4
5
6
7
8
9
/etc/cni/net.d/10-flannel.conf
{
"name": "cbr0",
"type": "flannel",
"delegate": {
"isDefaultGateway": true
}
}

🔅 最后,加入 cluster

1
kubeadm join --token cbc509.fcc110dc2d9c47d8 10.10.81.150:6443 --discovery-token-ca-cert-hash sha256:b7b0d7d737376b59417f2760e4c61bbb0948abee283677f83afc6d80fe67fff9

实就是运行最后一句话。

🎖 验收

在 master 上运行

1
$ kubectl get nodes

如何学习一门新技术

最近,因工作需要,专研云的相关技术,从技术的金字塔模型来看,云算是技术链顶端。

这也就造就了,学习和使用的人不多,会的人都在大公司,可交流的人不多。也就是有难度才有挑战。

那我是如何开始学习的呢?


🔅 一万小时定律

著名的一万小时定律指的是在一个特定的领域刻意训练达到一定时间,就会变成这个领域的顶尖人材。

但我想说的是这个理论的另外一面,你永远不是从零开始,宇宙万物是彼此关联,在一个领域的经验往往在进入另一个领域时也同样生效。

这一点很重要,消除恐惧感,对学习而言是很有效的。


🔅 学习的方法

☼ 提前准备的资源

  • Google / Github / 其它各种论坛

就开始搜索相关的资料,然后一并保存起来。形式零星的关键词记忆。

  • 一本相关好书

等有了一些基础知识时,再选一本这个领域的好书。我个人喜欢纸质书,因为这本书不是用来看的,而是一个归纳整理工具,这一点一会再讲到。


☼ 拼图理论

现代知识体系越来越趋于复杂化,像之前两三个做出一个小产品,到现在的动辄百人的开发团队协作,在云的相关技术更是这样,技术不再是单体,而是生态了。

那学习的方法也要跟得上,不然学到老也未必跟的上技术的迭代。

解决复杂性的方法有哪些呢,我个人用的一个叫拼图模型(也可以说是知识图谱)就是我把要学习的这个生态里的知识归类,做成拼图里的一个个小块,刚开始小块的边界很难划分,先不用太在意。在未来的时间里,这些块会趋向于完美。

当我们脑海中有这么一张拼图时(当然更建议用软件、笔把它画出来,这样更清晰),再看待一些东西,就会很清晰的知道它的位置、边界,重要性的定义,以及几样事物之前的关联。

而且更为重要的是,可以有区别的对待每一块的技术,在不周的时间上专研不同的块,还可以在某一个块不得要领时,换一个块去解决,为什么叫拼图理论,因为和拼图一样,有时你解决不了的一个地方,换去解决另一个地方,反而就把之前未能解决的问题给解决了。

当然,从管理学角度来看,这会引起另一个变革,清晰的块划分有利于分工合作。而合作带来整体效能的提升。

再回到刚提起的那本书,书的作用不再是细读,而是更快速的用前人总结好的知识体系,更快速的构建自己的知识架构拼图。

除了好书,构建拼图的好办法就是去听一些演讲,演讲从来都不能表达行为,都是用于概念的传播。听一个演讲就可以从别人的经验中完善自己的领域知识拼图。


☼ 学会排优先级

有了拼图模型,看着其中密密麻麻的块,这里最应该注意的是什么,是找到其中重要的点。

有些知识很重要,但可能在起步时反而用不到,有些是边边角角的技能反而是初始化的必要条件。

可以用管理学的重要和紧急二维模型,来给知识块进行优先级分类。

这样也可以大规模的降低启动的复杂度,合理的安排推进时间。


☼ 合作带来效能

有了拼图,划分清楚了边界,就有了合作的可能性。

然后,就是找到某些块的合作伙伴,或者在某个块上找到一些有经验的人、专家,迅速把这一个块给做掉。

比如说这次的学习中,有一个很大的块就是网络模型,这牵涉到很低层的网络知识体系,这一块就可以找到一个擅长网络的同事一起研究,于是两个人相互提升,扩充了自己的边界,也让事情的进展更快。


🔅 行动的力量

上面提到了很多学习的方法论,但技术的学习从来都不只是提升知识,而是多于实践,重于应用。

在本次中,一开始,就尝试着把 google / github 的代码去运行,计算机的好处就是永远可以低成本实践,对错永远分明。

为什么行动如此重要,因为别人的东西都不一定是对的,哪怕是最新的文章,都不一定能跑出正确的结果。要自己去多实践。

行动过程中的错误,更是一笔财富,无论是一本书,还是一篇文章,里面教的都是正确的路径,而如何解决错误,调试问题的能力则是行动过程中的最重要的。

毕竟理论和实践中总有那么一条鸿沟。


🔅 完成

在大的知识体系拼图的指引下,通过行动的力量,慢慢的,终于一嘬山一样的难题,就这样被解决了。

REF::

iPhone 屏幕一些事

今天看了下 iPhone X 的屏幕参数,发现它的分辨率达 2436 x 1125,和用的 DELL 2K 显示器几乎相同,但尺寸上的对比差别却是巨大。不得不感慨。

于是就总结了 iPhone 这些年的屏幕演变,一件很好玩的事情。


iPhone 2G, 3G, 3GS

从最初代的 iPhone 开始,Jobs 定下了一个尺寸 3.5 寸,认为这是人手最适合的大小,并一直的坚持着。

这时的工艺水平,还只是普通的高分屏水准。分辨率维持在 320 x 480,PPI 为 163

Spec

Size PPI Points Render @ Rendered Pixels Physical Pixels
3.5 163 320 x 480 1 320 x 480 -


iPhone 4, 4s

3GS 那圆坨坨的造型深入人心,于是在 4 的方正预告之时,办公时还出现过一些争论,不过人总是向前看的,4 的设计很快征服了所有人,特别是前后玻璃工业水平所带来的震撼,无与伦比!

4 还带来了视觉认知上的第一次升级,Retina 屏,也视网膜屏,这个原理很简单,就是以前固定大小放一个显示单位,现在放4个。视觉的改变就是颗粒感一下子就没有了,那清晰度,在两个手机间稍有对比,就会感动的落泪。

同样是 3.5 寸,分辨率变成了 640 x 960,起因是 PPI x 2,由 163 变成了 326。

Spec

Size PPI Points Render @ Rendered Pixels Physical Pixels
3.5 326 320 x 480 2x 640 × 960 -

苹果最牛的地方不是发明一个新东西,而是把这个已有的东西,从一些应用变成全民应用。像不像 Martin Flower


iPhone 5, 5s, 5c, SE

有人说 iPhone 5 是 Jobs 走前定下的最后一个手机的样式,所以是他最终的杰作。

5 出来时的变化,其实是让不少人也纠结了下的,多出一块下巴,是显示信息更多了,还是变得不好拿了。

不过当时的手机世界是 Apple 坚持不造大屏,所以其它手机在大屏的领域不断开拓,也有了不少的市场。

带给程序上的变化就是,以前简单的按点布局的方式不奏效了。要有一套新的布局系统了。就是 AutoLayout。不过更多人用的方式是,判断机型,如果是 5,就把高度算到 568,否则还是 480,又安稳的过了两年。

Spec

Size PPI Points Render @ Rendered Pixels Physical Pixels
4 326 320 × 568 2x 640 × 1136 -


iPhone 6, 6s, 7, 8

iPhone 6 的出现是讨人喜欢的,同时有 6 和 6 Plus,Apple 终于打破了 Samsung 在大屏的垄断,有人说这是没有了 Jobs 的后时代 Apple 在向市场妥协。

当时中国不在首发区,好多人从 HK,JP 进行代购,海关当时查的很紧,我的 日本代购的 6 Plus 还交了 550 的海关税,并且附带了3年的拍照咔嚓声。

6 时代的屏幕完全的变化了,Point 变为 375 x 667,PPI 依然为 326,也就是 2x 渲染,分辨率为 750 x1134

从此 Autolayout 完全被用上了。

Spec

Size PPI Points Render @ Rendered Pixels Physical Pixels
4.7 326 375 × 667 2x 750 × 1334 -


iPhone 6+, 6s+, 7+, 8 +

但 6 Plus 就很有意思了。

它的 Point 为 414 x 736 ,然后按照 @3x 渲染,理论上的分辨率应该是 1242 × 2208,但实际 PPI 仅为 401,物理分辨率 1920x1080,在向屏幕物理渲染时,要进行一次缩放 ÷1.15

于是这对 6 Plus 带来了不小的性能问题,3倍渲染,再 ÷1.15 缩放渲染,导致在动画多的应用中出现很多的卡顿。6s Plus 因为内顿升级为 2G,CPU 也进行了升级,卡顿要好太多。

为什么出现这样的渲染机制,猜想是更高 PPI 的屏幕成本较高,2@ 渲染又会降低清晰度。所以这就变成了这种方案。

这个模型被用了 4 年,直到 iPhone 10 周年时,Apple 依然用这个模型生产了 8和 8 Plus,不过可被看作为 iPhone X 这朵红花的绿叶吧。

Spec

Size PPI Points Render @ Rendered Pixels Physical Pixels
5.5 401 414 × 736 3x 1242 × 2208 1080 × 1920


iPhone X

iPhone X 的变化有种当年 4 出现时的感觉,砰然心动。

不过无边框屏在很多设备上已经有了,Apple 从来不会过于创新,只会在最合适的时机推出产品。

屏幕的变化是多了一个刘海,达 5.8 寸的 OLED 屏幕,不过由于是无边框屏,整机的尺寸与 8 差不多持平。

不过对渲染无变化,只是在设计时,要考虑一些东西。

屏幕的 Points 为 375 x 812,依然是 3x 渲染,分辨率为 1125 x 2436,不过这次 Apple 真的是做到了 3x PPI 的屏幕,高达 458 PPI,再无需向下缩放 1.15 倍了。这也是 iPhone X 售价高达 $999 的原因之一吧。

Spec

Size PPI Points Render @ Rendered Pixels Physical Pixels
5.8 458 375 × 812 3x 1125 × 2436 -


Spec 总览

Device Size PPI Points Render @ Rendered Pixels Physical Pixels
iPhone 2G,3G,3GS 3.5 163 320 x 480 1 320 x 480 -
iPhone 4,4s 3.5 163 320 x 480 1 320 x 480 -
iPhone 5,5s,5c,SE 4 326 320 × 568 2x 640×1136 -
iPhone 6,6s,7,8 4.7 326 375 × 667 2x 750 × 1334 -
iPhone 6+,6s+,7+,8+ 5.5 401 414 × 736 3x 1242 × 2208 1080 × 1920
iPhone X 5.8 458 375 × 812 3x 1125 × 2436 -


REF::