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

安装

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

配置 .zshrc

1
2
3
4
5
6
7
8
9
10
# 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


国内加速

  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::

也议全栈

当代社会的总趋势是越来越复杂,应对这种趋势的办法就是封装,把一个领域里的知识包装进一个黑盒,然后提供几个接口。

就像电视机,那里面的东西复杂的去了,但对用户来说,就一个遥控器。电视机就是一个封装,提供了遥控器就是这个接口。

封装就会带来行业的差异越来越细。还拿上个列子,遥控器里面也有一些组件,像微型电池,电池和遥控器电路板也是两个很专业的领域,他们两个通过一种接口在另一个维度进行合作,这个接口可被理解为微型电子的正负极和它的样式。

这种封装在人类的进化史上就叫分工。人的大脑有限,所能理解的事物也有限。所以人们只能复杂的世界中找到一个领域,然后进去专研。

分工和封装是同步的,人们也越来在越在自己的领域深挖,变成一个专业的人才。

但这也带来了一些问题,跨领域的沟通成本越来越高,有模式就有反模式。有些人会越来越深入单一领域,但有人就会跨领域发展,叫做通材。

专才和通才的没有优劣之分,也没有高下区别。

但时势不停发生变化,每个行业,每家公司总要经过几个不同的阶段,在这些阶段中,会随着需求的不同,而对不同的人的渴望度不同,所以就会表现出不行的价格,注意不是价值。

在计算机编程行业里,也遵循着这个理论。

在设备上,有 iOS,Android 和 Web,在层级上,有前端,中端,后端和数据库,在专业领域上,又有音视频,网络,压缩等。以上这些分类法只是用来举例。

在外界看来都是同一类的工程师,但里面的划分就有这么多种。所谓的隔行如隔山。因为太复杂了,只能继续通过接口来相互通信。

那个行业的通才呢,就是跨越多种语言,多种层级间的人,近几年有个专有名词,叫作全栈工程师。

从社会总理论上来讲,这个是一个发展的方向,但因为领域比较新,所以在这方面的认知还没达到统一,关于全栈的争论也就比较多。

UPS - 数据安全的必选项

最近上海的天是这样子的。

后果是偶尔停电,偶尔大雷下来之前,会跳电,空气开关会跳掉,对于一个有着家庭数据中心的人来说,真是心疼呀。要行动起来,用买买买来解决问题。

那这次要买的是什么呢,当然就是解决电的 UPS 啦。

目标

  • 体型较小并美观
  • 电的要求不高
  • 防雷,防浪涌
  • 断电后能通知 NAS / Mac 进行关机

看完这些,网上对比下来,其实很快就锁定了,只是细节都没有描述,也就是评测不多。于是只能自己买回来后进行评测了。

这里选中的是 APC-BR550G,APC 没听过,不过网上评价不错,而且群晖原生支持。

购买

京东下单,两天后才想起买的东西,原来并不是京东配送,而是其它一个小快递公司从广州配行的配送。

东西到了后,发现还是挺重的,看了下说明书,超级复杂,都好几类电源口,不过我的需求比较简单,只是接出一个插座,把 NAS,光猫,路由器,移动硬盘接在上面。

效果

APC 提供了一根数据线,一头是 RJ45 网线口,另一端是 USB,USB 接入 NAS 后,在后台设置中,立即就发现了 UPS,进行开启就可以了。

这就是最终效果图:

HiDPI 解析

最近研究显示器时,朋友一直在讲一个词,叫 HiDPI,于是做了些笔记。

几个参数

  • pixel/px 就是显示器上一个独立可以显示的点,显示器由很多这样的点排列组成。
  • point/pt point 在一个指标下就是一个固定的长度,比如说 72,就是指 1/72 英寸,在普通显示情况下,pt值是等于px 值的。但一些情况不行了,像retina 下,4个 px 才等于一个 pt。
  • PPI(Pixels per inch) 是每英寸上的点数,跟尺寸无关,越大显示的点越多,就越清晰。
  • Display resolution 显示器上一共有的点数,和尺寸有关,也和 PPI 有关,是 PPI * 尺寸

iPhone 6 Plus 诡异的分辨率

iPhone 6 和 6+ 虽然是大小差不多,但在意义上却有点不尽相同,感觉 6+ 更接近一个 iPad Mini。

6 在显示参数上和 5S 是保持一致的,像 PPI 都是 326,也就是经典的 @2x 素材,只是物理屏幕变化, Point 也相应的增大为 375 × 667。设计师要做的就是注意尺寸变大后的留白,开发所要做的就是用相对布局。

6+ 就不同了,在渲染时用的是 @3x,理论上,PPI 应该是 163*3=489, Points 为 736 x 414,对应 3x 分辨率应为 2001x1125。但实际上,PPI 为 401;分辨率为 1920x1080,是标准的 PPI x 2.46,

在实际渲染时,6+ 也是先按照 @3x 2001x1125 来渲染,然后整体缩放 1.15 倍,到 1920*1080

显示器分辨率

从 iPhone 为入口再来理解显示器 HiDPI。

所以最好的情况只有一种,就是显示器上,用显卡渲染出四个点,做成一个 Point,对应到显示器的4个点上。这也是 Retain 的显示原理。这样是最清晰的,显示所渲染的每一个点都能在显示器上还原。

然后就是让显卡渲染出显示器的分辨率,一个点做为一个 point,这样的利用率是最高的,但字就会比较小。

举个例子,我们常见的 2.5K, 4K, 5K 分辨率。

设备 设备分辨率 HiDPI
2.5K 2560x1440 1280x720
4K 3840x2160 1920x1080
5K 5120x2880 2560x1440

以我最爱的分辨率 2.5K 来看,那开启 HiDPI 后的最佳分辨率就是 5K。

但 4K 分辨率依然可以按 2.5K 来进行 HiDPI 来渲染,那是怎么回事呢?

答案很简单,和 iPhone 7+ 的原理是一样的,显卡仍按 @2x 5120x2880 来渲染,然后再进行缩放到 3840x2160 进行显示。当然这样会造成显示的不清晰,同时也会产生资源的浪费

Macbook Pro - Retina display

先来看看 rMBP 15 的显示参数:

13.3-inch (diagonal) LED-backlit display with IPS technology; 2560-by-1600 native resolution at 227 pixels per inch with support for millions of colors

Supported scaled resolutions:

  • 1680 by 1050
  • 1440 by 900
  • 1280 by 800
  • 1024 by 640

从以上参数来看,它的显示器分辨率是 2560x1600,比 DELL U2515 还高,但因为屏幕小,所以官方认为这么高的分辨率没有什么意义,就在选项中只支持了四种分辨率。

所以,它理论上都是开启 HiDPI 的,1280x800 是它最佳分辨率。其它分辨率都要进行一些显示上的缩放。

REF::