我的 2022 书单

今年上半年突然多了很多时间看书,先把以前囤的纸质书翻了翻,后来又买了一个华为出品的墨水屏,立刻荣升阅读主力。
总的来说搭配微信阅读 App 的付费会员还是比较省钱的,预计今年底就能回本。

读完:

《原则2》

居家隔离 7 天的时候读完,大循环的概念可以说是非常实用的长期投资参考

《一本书读懂从来没懂过的经济学》

以前确实忽视了理财,导致薪资占收入的几乎 100%,现在渐渐能看懂财报了,也学着投资黄金和基金

《隐秘战争》

《美国陷阱》

长臂管辖太强大了,美国政府的压倒性制裁普通公司根本扛不住,国家才是坚强的后盾

看本书我还整理了一个 美国刑事司法制度流程图 需要自取,233333

《金融战争》

书架上翻出来的一本已经泛黄的盗版书,里面的内容还是有点阴谋论的味道的

《创新公司:皮克斯的启示》

给自媒体公司如何保持作品质量、避免创意枯竭带来很多启示

《斯蒂夫·乔布斯传》

RIP Jobs, 再次读已经是 10 年后了,很多东西都发生了变化,苹果也不再是原来的苹果
连 Apple Music 都可以在备受鄙夷的 Android 系统上使用了

《创新者的窘境》

据说是乔布斯读了好多遍的书(另外一本是关于禅修的,无感),怎么发展延续性和破坏性创新,这可能就是苹果至今保持市值的秘密

《乌合之众》

失智的人群

《无厂模式》

通过这本书可以大概了解芯片的产业链,尤其是最新的以 TSMC 为代表的的 Fabless 模式

《芯片先进封装》

稀有气体在芯片制造业中的应用

《奇妙的化学元素》

复习高中化学知识了属于是

《没有工作的世界》

《人体简史》

《看懂芯片原来这么简单》

《汽车是怎样跑起来的》

好像从来没有研究过汽车是怎么跑起来的

《寄生虫星球》

寄生虫太可怕了! 寄生虫和情绪的关系令人称奇。

《看一眼你就会笑》

好笑是好笑,就是有的梗有点重复了

《生活蒙太奇》

画风很减压

《从0到无穷》

数学啊数学

《挽救计划》

期待电影

《月背征途》

嫦娥和月兔怎么做敏捷迭代的

《识别急症陷阱》

《癌症·真相》

跨国治疗癌症指南

《牛津通识读本:免疫系统》

《牛津通识读本:细胞》

很担心身边的家人、朋友会得癌症,提前做一些知识储备

在读:

《沃兹传:与苹果一起疯狂》
《癌症传》
《国富论》
《美联储》
《微积分的力量》
《财报就像一本故事书》
《枪炮、病菌和钢铁》
《算力时代:一场新的产业革命》

[译] GPS 工作原理的可视化

之前订阅了 ciechanowski 大神的博客,虽然更新不频繁(一年几篇)但是每一篇都属于精品。大神总是能结合前端可视化技术把一些天文、物理什么的知识点讲解的很透彻。

GPS 这篇文章刚发布没几天我就注意到了,赶紧拜读了一下。从三角定位讲到相对论再到卫星信号的编解码,内容非常详实,交互动画的制作精美无比可以说是教案水准。

因为某些原因,2022 这个春节能够在家待上好长一阵子。所以我就利用这个机会前后花了大概 10 天时间把这篇文章翻译成了 中文版,希望能够对所有对 GPS 工作原理感兴趣的朋友带来帮助。

文章地址

pages.longtian.info/gps

为什么 Docker Pull 显示的 Digest 和 Docker Hub 的不一样

这个问题困扰自己很久了,今天终于搞明白了。

复现: 在 Docker Hub 查看 alpine 的镜像信息,并在主机上执行 docker pull alpine:3.12
问题: 在网站上看到的 Digest 和控制台里显示的 Digest 不一样

控制台显示的 Digestd9459083f962de6bd980ae6a05be2a4cf670df6a1d898157bceb420342bec280

1
2
3
4
3.12: Pulling from library/alpine
Digest: sha256:d9459083f962de6bd980ae6a05be2a4cf670df6a1d898157bceb420342bec280
Status: Image is up to date for alpine:3.12
docker.io/library/alpine:3.12

这个 Digest 并不在 alpine:3.12 官方仓库对应不同平台的镜像列表

DIGEST OS/ARCH COMPRESSED SIZE
7893969ec350 linux/386 2.67 MB
74e5ad84a67e linux/amd64 2.68 MB
8a6e4b2093ee linux/arm/v6 2.49 MB
45a18fc6f681 linux/arm/v7 2.31 MB
29ba524fa7f5 linux/arm64/v8 2.59 MB
08340ab4a605 linux/ppc64le 2.69 MB
9a7276e7579f linux/s390x 2.46 MB

那么这两个 Digest,到底以那个为准呢

  • d9459083f962de6bd980ae6a05be2a4cf670df6a1d898157bceb420342bec280
  • 74e5ad84a67e9b8ed7609b32dc2460b76175020923d7f494a73a851446222d18

试着都拉取一下,发现都可以

1
2
docker pull alpine@sha256:d9459083f962de6bd980ae6a05be2a4cf670df6a1d898157bceb420342bec280
docker pull alpine@sha256:74e5ad84a67e9b8ed7609b32dc2460b76175020923d7f494a73a851446222d18

它们对应的镜像都是 alpine:3.12 可以说是一样但又不完全一样。这点可以通过 docker image inspect alpine:3.12 验证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[
{
"Id": "sha256:b0925e0819214cd29937af66dbaf0e6fe239997faea60922cc890f9984512507",
"RepoTags": [
"alpine:3.12"
],
"RepoDigests": [
"alpine@sha256:74e5ad84a67e9b8ed7609b32dc2460b76175020923d7f494a73a851446222d18",
"alpine@sha256:d9459083f962de6bd980ae6a05be2a4cf670df6a1d898157bceb420342bec280"
],
"Created": "2021-11-12T17:20:08.442217528Z",
"Container": "385e1cc96cc7482dfb6847e293bb24baecd3f48a49791b9b45e297204b160287",
"...": {}
}
]

也就是两个 Digest 都对应着 Docker 官方打包机在 2021-11-12T17:20:08.442217528Z 这个时间点打包出来的镜像的 ID

1
2
3
4
构建链接(会被定期删除)
https://doi-janky.infosiftr.net/job/multiarch/job/amd64/job/alpine/lastSuccessfulBuild/artifact/build-info/image-ids/alpine_3.12.txt
镜像 ID
sha256:b0925e0819214cd29937af66dbaf0e6fe239997faea60922cc890f9984512507

那么不同的 Digest 是怎么对应同一个镜像的呢,这就要查看 Docker Registry 的 API 文档并了解 Docker 拉取镜像的原理了。

结论

docker pull 显示的是跨平台的 manifest list 的 Digest,而 Docker Hub 显示的是各个平台 manifest 的 Digest

请求对象 MIME
manifest list application/vnd.docker.distribution.manifest.list.v2+json
manifest application/vnd.docker.distribution.manifest.v2+json

可以通过 curl 指定不同的 Accpet HTTP 请求头参数测试

1
2
3
4
5
curl https://registry.mirror.aliyuncs.com/v2/library/alpine/manifests/3.12 \
-H "Accept: application/vnd.docker.distribution.manifest.list.v2+json" -v

curl https://registry.mirror.aliyuncs.com/v2/library/alpine/manifests/3.12 \
-H "Accept: application/vnd.docker.distribution.manifest.v2+json" -v

Manifest List

Response Header

1
2
3
4
5
Content-Type: application/vnd.docker.distribution.manifest.list.v2+json
Content-Length: 1638
Docker-Content-Digest: sha256:d9459083f962de6bd980ae6a05be2a4cf670df6a1d898157bceb420342bec280
Docker-Distribution-Api-Version: registry/2.0
Etag: "sha256:d9459083f962de6bd980ae6a05be2a4cf670df6a1d898157bceb420342bec280"

Response Body

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
{
"manifests": [
{
"digest": "sha256:74e5ad84a67e9b8ed7609b32dc2460b76175020923d7f494a73a851446222d18",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "amd64",
"os": "linux"
},
"size": 528
},
{
"digest": "sha256:8a6e4b2093eee6246fdd5181cfcad4b587db1068c93315ccf5366f79d8117485",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "arm",
"os": "linux",
"variant": "v6"
},
"size": 528
},
"..."
],
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"schemaVersion": 2
}

Manifest

Response Header

1
2
3
4
5
Content-Type: application/vnd.docker.distribution.manifest.v2+json
Content-Length: 528
Docker-Content-Digest: sha256:74e5ad84a67e9b8ed7609b32dc2460b76175020923d7f494a73a851446222d18
Docker-Distribution-Api-Version: registry/2.0
Etag: "sha256:74e5ad84a67e9b8ed7609b32dc2460b76175020923d7f494a73a851446222d18"

Response Body

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 1471,
"digest": "sha256:b0925e0819214cd29937af66dbaf0e6fe239997faea60922cc890f9984512507"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 2809473,
"digest": "sha256:8572bc8fb8a32061648dd183b2c0451c82be1bd053a4ea8fae991436b92faebb"
}
]
}

Kubernetes 资源分配

最近遇到了 Kubernetes 上的 Web 应用响应时间长尾的问题,上网收集资料,分析了一下原因。

  • CGroup 节流导致 Web 请求相应时间增加
  • 当前执行的 CPU 不断变动
  • 核数越多 CGroup 补偿碎片越严重
  • 跨越 NUMA 节点

请求和限制计算资源

Kubernetes 对资源的细粒度管理功能可以说是吸引传统应用上 Kubernetes 的重要原因之一

我们在定义 Pod 的时候通过以下两个字段可以控制计算资源的分配:

字段 备注
requests 请求的最少资源
limits 资源限制
  • requests 无法满足时,Pod 会一直停在 Pending 状态,触发 FailedScheduling 事件
  • 当使用内存资源超过 limits 时,如果有其他 POD 需要内存,则使用内存最多的容器会被 sacrifice,触发 OOMKilled 事件
  • 如果不指定 limits , 在没有命名空间默认值的情况下可以无限制地使用资源
  • 如果指定了 limits , 但是没有指定 requests , 则 requests 值默认为 limits
  • 从分配角度讲 CPU 属于可以压缩的资源、内存属于不可以压缩的资源

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Pod
metadata:
name: memory-demo
namespace: mem-example
spec:
containers:
- name: memory-demo-ctr
image: polinux/stress
resources:
limits:
memory: "200Mi"
requests:
memory: "100Mi"
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"]

命名空间默认值、最小值和最大值

有些 Kubernetes 集群环境下会出现这样的问题:定义 Pod 的时候明明没有给计算资源的限制,但是在实际运行的 Pod 上出现了资源限制的定义。

这很有可能是命名空间的默认值造成的。 通过 LimitRange 对象可以设置命名空间资源的默认值、最小值和最大值。

关键字 备注
default limit
defaultRequest request
min 最小值
max 最大值
maxLimitRequestRatio 比率
type 对象

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: LimitRange
metadata:
name: mem-min-max-demo-lr
spec:
limits:
- default:
memory: 1Gi
defaultRequest:
memory: 1Gi
max:
memory: 1Gi
min:
memory: 500Mi
type: Container

Pod 服务质量

当系统资源不足时就会有 Pod 遭殃,但是那么多 Pod 怎么知道要干掉哪一个呢。这就和 Pod 的服务质量有关。

查看运行中的 Pod 会有一个 qosClass 字段,它有以下三种取值:

类型 备注
Guaranteed 被保证的,优先级最高,除非超过限制不然不会被干掉
Burstable 允许突发,位于中间, 当系统资源不足且没有 Best-Effort 级别时
BestEffort 尽力保证,优先级最低,当系统资源不足时最先被干掉

需要注意 qosClass 的值无法直接设置,而是通过 requestslimits 组合隐式设置

Guaranteed

  • Pod 中的每个容器都要指定 CPU 请求和限制,并且两者相等
  • Pod 中的每个容器都要指定内存请求和限制,并且两者相等

Burstable

  • Pod 不符合 Guaranteed QoS 类的标准
  • Pod 至少有一个容器具有 CPU 或内存请求

BestEffort

  • 没有 CPU、内存请求和限制

CPU 管理策略

在现代操作系统的设计下,负载运行可能会不断地迁移到不同的 CPU 核心,Kubernetes 上也是一样。

Kubernetes 提供了 CPU 管理策略来实现负载和 CPU 核的绑定。

通过 kubelet 参数 --cpu-manager-policy 来指定 CPU 管理策略。

取值 备注
none 默认策略,按照 CFS 来执行调度
static 允许为节点上具有某些资源特征的 Pod 赋予增强的 CPU 亲和性和独占性

为了将 Pod 绑定到 CPU 需要

  • Kubelet 开启 --cpu-manager-policy static
  • Pod 的服务优先级为 Guaranteed
  • Pod 的 CPU 资源为整数

例如,下面的 Nginx Pod

1
2
3
4
5
6
7
8
9
10
11
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
memory: "200Mi"
cpu: "2"
requests:
memory: "200Mi"
cpu: "2"

在运行时可独占两颗 CPU

NUMA 和拓扑管理策略

NUMA 是系统优化的一个常用思路,它是伴随着多处理器出现的一个问题。

非统一内存访问架构(英语:Non-uniform memory access,简称NUMA)是一种为多处理器的电脑设计的内存架构,内存访问时间取决于内存相对于处理器的位置。

常见的硬件有

  • CPU
  • Memory
  • GPU
  • NIC

安装 numactl 后可以通过下面的命令查看主机的 NUMA 相关信息

numactl --harware

通过下面的命令查看 NUMA 的统计信息

numastat

1
2
3
4
5
6
numa_hit       | Number of pages allocated from the node the process wanted.
numa_miss | Number of pages allocated from this node, but the process preferred another node.
numa_foreign | Number of pages allocated another node, but the process preferred this node.
local_node | Number of pages allocated from this node while the process was running locally.
other_node | Number of pages allocated from this node while the process was running remotely (on another node).
interleave_hit | Number of pages allocated successfully with the interleave strategy.

以上指标均可以通过 Telegraf 监控

1
[[inputs.kernel_vmstat]]

--cpu-manager-policy static --topology-manager-scope pod --topology-manager-policy single-numa-node

参考

Istio 请求响应标志

整理了一下 Istio 的 Response Flags

介绍

源码

协议 缩写 备注 备注
HTTP DC DOWNSTREAM_CONNECTION_TERMINATION Downstream connection termination.
HTTP DI DELAY_INJECTED The request processing was delayed for a period specified via fault injection.
HTTP DPE DOWNSTREAM_PROTOCOL_ERROR The downstream request had an HTTP protocol error.
HTTP FI FAULT_INJECTED The request was aborted with a response code specified via fault injection.
HTTP IH INVALID_ENVOY_REQUEST_HEADERS The request was rejected because it set an invalid value for a strictly-checked header in addition to 400 response code.
HTTP LH FAILED_LOCAL_HEALTH_CHECK Local service failed health check request in addition to 503 response code.
HTTP LR LOCAL_RESET Connection local reset in addition to 503 response code.
HTTP/TCP NC NO_CLUSTER_FOUND Upstream cluster not found.
HTTP/TCP NR NO_ROUTE_FOUND No route configured for a given request in addition to 404 response code, or no matching filter chain for a downstream connection.
HTTP RL RATE_LIMITED The request was ratelimited locally by the HTTP rate limit filter in addition to 429 response code.
HTTP RLSE RATELIMIT_SERVICE_ERROR The request was rejected because there was an error in rate limit service.
HTTP SI STREAM_IDLE_TIMEOUT Stream idle timeout in addition to 408 response code.
HTTP UAEX UNAUTHORIZED_EXTERNAL_SERVICE The request was denied by the external authorization service.
HTTP UC UPSTREAM_CONNECTION_TERMINATION Upstream connection termination in addition to 503 response code.
HTTP/TCP UF UPSTREAM_CONNECTION_FAILURE Upstream connection failure in addition to 503 response code.
HTTP/TCP UH NO_HEALTHY_UPSTREAM No healthy upstream hosts in upstream cluster in addition to 503 response code.
HTTP UMSDR UPSTREAM_MAX_STREAM_DURATION_REACHED The upstream request reached to max stream duration.
HTTP/TCP UO UPSTREAM_OVERFLOW Upstream overflow (circuit breaking) in addition to 503 response code.
HTTP UPE UPSTREAM_PROTOCOL_ERROR The upstream response had an HTTP protocol error.
HTTP UR UPSTREAM_REMOTE_RESET Upstream remote reset in addition to 503 response code.
HTTP/TCP URX UPSTREAM_RETRY_LIMIT_EXCEEDED The request was rejected because the upstream retry limit (HTTP) or maximum connect attempts (TCP) was reached.
HTTP UT UPSTREAM_REQUEST_TIMEOUT Upstream request timeout in addition to 504 response code.

JVM 对容器化支持的参数

背景知识

阅读本文你需要了解

  • JVM 的启动参数
  • 容器的启动参数

JVM 启动参数

参数 备注
-Xms MiniumHeapSize
-Xmx MaxHeapSize
-Xss StackSize

在容器化之前一般由运维工程师根据部署环境机器的内存大小来调整,并写在启动脚本里

容器启动参数

在使用 k8s 编排容器的时候通常通过定义 resources 字段来限制容器使用的资源

1
2
3
4
resources:
limit:
cpu: 1000m
memory: 1G

为了方便验证,我们直接用本地 docker 来实验,docker run 命令的一些参数

参数 备注
–cpus Number of CPUs
–cpu-period int Limit CPU CFS (Completely Fair Scheduler) period
–cpu-quota int Limit CPU CFS (Completely Fair Scheduler) quota
–cpu-rt-period int Limit CPU real-time period in microseconds
–cpu-rt-runtime int Limit CPU real-time runtime in microseconds
–cpu-shares int CPU shares (relative weight)
–memory Memory limit

问题

容器化的 JVM 程序会遇到这几个问题

  • 问题 1: 超过了容器的资源限制被 OOM 机制杀掉
  • 问题 2: 在核数比较高的机器上 JVM 频繁 GC

问题 1

JDK 8u191 之前这个问题很常见,主要是由于 JVM 获得的最大内存是主机的最大内存而不是容器的最大可用内存,解决方法是修改启动逻辑。
让 JVM 能够获取到正确的最大可用内存

问题 2

ParallelGC 线程数如果过高会导致 GC 过于频繁,和问题1 类似,这是由于 JVM 获得的可用核数不正确导致的。

为了解决这两个需要同时修改容器的资源限制和 JVM 的启动参数,并使之相匹配。
当然也有一种不那么笨的做法是引入一个动态的启动脚本,先根据容器的实际资源限制算出对应的 JVM 参数再去启动 JVM。
JDK 社区已经注意到以上这几个容器化常遇到的问题并在新的 JDK 版本里解决了,由于 JDK8 的超长待机时间,以下便以 JDK8 为例

版本 GA 时间 验证镜像 容器化支持
8u191 2018-10-16 openjdk:8u191-alpine 支持
8u131 2017-04-18 openjdk:8u131-alpine 实验性支持
8u121 2017-01-17 openjdk:8u121-alpine 不支持

识别 CPU 资源

实验: 通过 ParallelGCThreads 是否符合预期来验证 JDK 对 CPU 资源的识别是否正确

1
java -XX:+PrintFlagsFinal -XX:+UseParallelGC -version | grep -i ParallelGCThreads

8u191 之前不支持

1
docker run --cpus 1 --rm openjdk:8u121-alpine java -XX:+PrintFlagsFinal -XX:+UseParallelGC -version | grep -i ParallelGCThreads

结果不符合预期,结果为主机核数

8u191 支持识别可用 CPU 核数

1
docker run --cpus 1 --rm openjdk:8u191-alpine java -XX:+PrintFlagsFinal -XX:+UseParallelGC -version | grep -i ParallelGCThreads

结果符合预期,结果为 1

8u191 关闭自动识别可用 CPU 核数

在分到 4 个 CPU 核数的情况下,指定可用 CPU 核数为 1

1
docker run --cpus 4 --rm openjdk:8u191-alpine java -XX:+PrintFlagsFinal -XX:+UseParallelGC -XX:ActiveProcessorCount=1 -version | grep -i ParallelGCThreads

结果符合预期,结果为 1

识别 Memory 资源

实验: 通过查看 JVM 运行时的参数来验证 JVM 对 Memory 资源的识别是否正确

java -XshowSettings:vm -version

8u121 不支持

1
docker run -m 100MB --rm openjdk:8u121-alpine java -XshowSettings:vm -version

结果不符合预期,结果为主机内存的 25%,这是因为这个版本 JDK 的 MaxHeapSize 计算公式为:

1
MaxHeapSize = MaxRAM / MaxRAMFraction

其中的 MaxRAM 来自于主机

8u131 实验性支持

直接使用 8u131 版本的 JDK 运行

1
docker run -m 100MB --rm openjdk:8u131-alpine java -XshowSettings:vm -version

结果不符合预期,说明 8u131 默认没有开启对容器化的支持,要开启需要加上实验性参数

1
docker run -m 100MB --rm openjdk:8u131-alpine java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XshowSettings:vm -version

结果符合预期

8u191 默认支持

使用 8u191 版本运行

1
docker run -m 100MB --rm openjdk:8u191-alpine java -XshowSettings:vm -version

结果符合预期

8u191 关闭自动识别最大可用内存

1
docker run -m 100MB --rm openjdk:8u191-alpine java -XX:-UseContainerSupport -XshowSettings:vm -version

结果符合预期,结果为主机内存的 25%

JVM 容器化相关参数

1
2
3
4
5
docker run -m 100M --rm openjdk:8u191-alpine java \
-XX:+UnlockExperimentalVMOptions \
-XX:+UnlockDiagnosticVMOptions \
-XX:+PrintFlagsFinal \
-XX:+UseParallelGC -version | grep -i 'cgroup\|container\|parallelgcthreads'

ActiveProcessorCount

Specify the CPU count the VM should use and report as active

8U191 引入

PrintContainerInfo

必须使用 UnlockDiagnosticVMOptions 开启

1
docker run -m 100MB --rm openjdk:8u191-alpine java -XshowSettings:vm -XX:+UnlockDiagnosticVMOptions -XX:+PrintContainerInfo -version

UseContainerSupport

Enable detection and runtime container configuration support

8U191 引入

PreferContainerQuotaForCPUCount

Calculate the container CPU availability based on the value of quotas (if set), when true. Otherwise, use the CPU shares value, provided it is less than quota.

UseCGroupMemoryLimitForHeap

Use CGroup memory limit as physical memory limit for heap sizing

8U131 引入

在 JDK11 中已移除,被默认开启的 UseContainerSupport 代替

MinRAMFraction

Minimum fraction (1/n) of real memory used for maximum heap size on systems with small physical memory size.
Deprecated, use MinRAMPercentage instead

8U131 引入 8U191 移除

MaxRAMFraction

Maximum fraction (1/n) of real memory used for maximum heap size.
Deprecated, use MaxRAMPercentage instead

8U131 引入 8U191 移除

InitialRAMPercentage

Percentage of real memory used for initial heap size

8U191 引入

MaxRAMPercentage

Maximum percentage of real memory used for maximum heap size

MaxRAMPercentage 在可用内存大于 250MB 时生效

1
docker run -m 1000MB --rm openjdk:8u212-alpine java -XshowSettings:vm -XX:MaxRAMPercentage=50.0 -XX:MinRAMPercentage=50.0 -version

MinRAMPercentage

Minimum percentage of real memory used for maximum heap size on systems with small physical memory size

MaxRAMPercentage 和 MaxRAMPercentage 都是用来计算 MaxHeapSize 的

MinRAMPercentage 在可用内存小于 250MB 时生效 (实际很少除非 IoT 设备)

1
docker run -m 200MB --rm openjdk:8u212-alpine java -XshowSettings:vm -XX:MaxRAMPercentage=50.0 -XX:MinRAMPercentage=50.0 -version

参考

腾讯软件源

1
2
3
wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.cloud.tencent.com/repo/centos7_base.repo
wget -O /etc/yum.repos.d/epel.repo http://mirrors.cloud.tencent.com/repo/epel-7.repo
sed -i 's/cloud\.tencent\.com/tencentyun\.com/1'

[译] Let's Encrypt 的新根证书和中间证书

原文

在 2020 年 9 月 30 日这天,Let’s Encrypt 颁发了六个新证书:一个根证书,四个中间证书和一个交叉证书。这些证书属于改善网络隐私的更大计划的一部分,让 ECDSA 终端证书被更广泛的采纳,和更小的证书体积。

鉴于我们每天要颁发 150 万张证书,这些证书有什么特别?为什么颁发它们?怎么颁发它们?让我们通过解释 CA 是如何思考和工作的来回答这些问题。

背景

每一个被公众信任的 CA (例如 Let’s Encrypt) 都至少有一个根证书被众多的浏览器和操作系统 (例如 Mozilla 和 Google) 的信任跟存储所包含。通过这种方式,用户可以确认他们从网站收到的证书是一个被浏览器信任的机构所颁发的。但是根证书由于它们的广泛传播以及较长的信赖周期,它的秘钥必须被妥善的保护并离线保管,以防止被用来不停地签发。因此 CA 会有若干个中间证书来替代根证书以确保证日常安全。

最近 5 年里, Let’s Encrypt 只有一个根证书: ISRG Rott X1,它拥有一个 4096 位的 RSA 秘钥并且有效期直到 2035 年。

也是在这段时间里,我们有了四个中间证书,分别是 Let’s Encrypt Authorities X1, X2, X3 和 X4。 前两个是 Let’s Encrypt 刚开始运营的 2015 年颁发的,有效期为 5 年;后两个是一年后,也就是 2016 年颁发的,有效期 5 年,并将在明年的这个时候过期。所有的这些中间证书都使用 2048 位的秘钥。此外,这些中间证书都由 IdenTrust 公司的 DST Root CA X3 根证书交叉签署,这个证书由另一家广泛被信任的 CA 所管理。

Let’s Encrypt 到 2020 年 8 月的结构图

8月

新证书

首先,我们颁发了两个 2048 位秘钥的 RSA 中间证书:R3 和 R4. 这两个证书都由 ISRG Root X1 签署,拥有 5 年的有效期。同样交由 IdentTrust 交叉签署。它们实际上是 X3 和 X4 的直接替代,考虑到它们一年后即将到期。我们预计会在今年底把主要的证书颁发流水线切换到使用 R3 证书,不会对证书的颁发和续签的造成实际的影响。

另一个新证书更有意思一点。首先,我们有了一个新的 ISRG Root X2,将会使用 ECDSA P-384 替代 RSA,有效期到 2040 年。由它颁发了两个新的中间证书:E1 和 E2,签名算法也是 ECDSA 并且有效期为 5 年。

值得注意的是,这些新中间证书并没交由 IdentTrust 的 DST Root CA X3 交叉签署, ISRG Root X2 本身由 ISRG Root X1 交叉签署。敏锐的观察者可能也会注意到我们没有通过 ISRG Root X2 颁发有 OCSP 签名的证书。

Let’s Encrypt 到 2020 年 9 月的结构图

9月

既然已经讨论到了技术细节,不妨再深入了解下这个结构的由来。

为什么颁发 ECDSA 的根证书和中间证书

已经有好多文章讨论过 ECDSA 的好处 (相同加密程度下更小的秘钥,更快的加密,解密,签名,验证等操作)。不过对我们来说,更大的好处来自于证书体积的缩小。

每一个通过 https:// 到远程主机的链接都需要一次 TLS 握手。每一个 TLS 握手都需要服务器提供它的证书。校验证书的过程还包括检查证书链(包括直到可信根证书的所有中间证书),这通常也是由有服务器提供的。这就意味这每个链接,一个覆盖各种广告和跟踪像素的页面会包含几十甚至上百个链接,这会需要传输大量的证书数据。并且每个证书都包含自己的公钥和签发者的签名。

一个 2048 位的 RSA 公钥大概 256 字节,而一个 ECDSA P-284 的公钥只有 48 字节。类似的, RSA 的签名会需要额外的 256 字节,而 ECDSA 只需要 96 字节。再考虑到其他一些开销,每个证书能节约大约 400 字节。用这个数乘以你的证书链长度以及每天的链接数,带宽的节省就很可观了。

这些省下的流量不仅会使我们的证书使用者每月节省大量的带宽费用,也惠及那些有限的、受限的终端用户。提高整个互联网的隐私性不仅是采用证书技术,也包括这让这些技术更经济。

此外由于我们很关心证书的大小,我们还采取了一些其它手段来让证书变得更小。我们把证书的主体名字由 “Let’s Encrypt Authority X3” 缩减到 “R3”,这是给予机构名称里已经提供了冗余的 “Let’s Encrypt”。同时我们还缩短了 Authority Information Access Issuer 和 CRL Distribution Point 的 URL 长度,我们还整个砍掉了 CPS 和 OCSP 的 URL。通过这些手段我们能够在不丢失实质性信息的情况下让证书再缩小 120 字节。

为什么交叉签署 ECDSA 根证书

交叉签署是新根证书从被签发到被主流信任的过渡阶段很重要的步骤。我们知道 ISRG Root X2 可能需要 5 年甚至更长时间才能被广泛接受,所以 E1 中间证书签发的证书如果希望被信任,一定需要证书链中某处的交叉签署。

我们基本上有两种方法:用现有的 ISRG Root X1 交叉签署 ISRG Root X2,或者用 ISRG Root X1 直接交叉签署 E1 和 E2。接下来我们就来分析下这两种做法分别有什么优缺点。

交叉签署 ISRG Root X2 意味着如果一个用户在信任库里有 ISRG Root X2,那么该证书链就是 100% ECDSA ,可以利用前文所述的快速校验。并且在接下来的几年里随着 ISRG Root X2 被加到越来越多的信任库里,ECDSA 终端证书的验证会越来越快而不需要用户或者网站做什么。这么做的代价是,只要 X2 还不在信任库里,用户的客户端就需要验证两个中间证书包括 E1 和 X2 直到 X1 根证书。这显然增加了证书验证的时间。

直接交叉新签署中间证书也有问题。一方面所有证书链的长度是相同的,在证书使用者和被广泛信任的 ISRG Root X1 之间只有一个中间证书。但是另一方面,随着 ISRG Root X2 获得越来越广泛的的信任,我们需要通过切换到另外一个链来
保证所有都能享受全链 ECDSA 的好处。

最终我们认为全链 ECDSA 更重要,所以我们选择了第一个方案,交叉签署 ISRG Root X2 证书。

为什么我们不提供 OCSP Responder 了

OCSP 协议是用户客户端用来发现并实时检查证书是否被吊销的一种方式。无论何时,一个浏览器如果想要知道证书是否有效,它可以通过访问证书里的一个 URL 就能得到是或否的答案,这个结果是由另一个可以被用相同方式检查的证书签名。这对终端用户的证书来说是非常棒的,应为请求响应体积很小并且速度很快。根据访问的站点不同,任何一个用户可能会关心(因此必须下载)海量证书集合的有效性。

但是中间证书只是海量证书的一个小小的子集,并且通常是广为人知的,也很少被吊销。因此,提供一个所有常用中间证书的吊销列表可能会更有效。我们的中间证书都包括一个 URL,通过这个 URL 浏览器可以下载证书的 CRl。实际上有些浏览器甚至会在例行更新里带上 CRL 列表集合,这使得在检查中间证书有效性的时候不需要再进行一次额外的访问开销,从而为大家创造更好的体验。

实际上用来指导 CA 的 Baseline Requirements 最近一次更新表明,中间证书已经不再强制要求包含一个 OCSP URL,而是可以只通过 CRL 来发布自己的吊销信息。鉴于此,我们从中间证书里移除了 OCSP URL,即我们不再需要为所有 ISRG Root X2 颁发的中间证书提供 OCSP Responder。

总结

至此我们已经介绍了新证书,最后再提一点:我们是怎么颁发这些证书的。

创建新根证书和中间证书是一个大事件,因为它们是被监管的的并且需要极其小心地看管好秘钥。这个事件是如此重要以至于颁发新证书被称作是“仪式”。在 Let’s Encrypt 我们非常推崇机器自动化,所以我们想要这个仪式的人为干预越少越好。

在过去的几个月里我们为这个仪式建立了一个工具,如果输入正确的配置,就能够生成所需的秘钥、证书和交叉签名请求等。我们还建立了一个仪式的 Demo 来说明这个配置文件可以并且允许所有人来运行和检查结果。我们的 SRE 搭建了一个相同并配有硬件安全模块的网络,自己执行了若干次仪式以确保整个流程完美无暇。我们与技术委员会、社区和若干邮件列表分享了这个 Demo,在这个过程中获得了很多有价值的反馈,其中一些甚至影响了上面我们提到的一些决定。最终、在 2020 年 9 月 3 号,我们的执行董事和 SRE 在一个安全的数据中心碰面并执行了整个仪式,并且有录像以供审计用。

现在仪式已经完成。我们已经更新了证书页面上关于新证书的细节,并且开始着手于申请将我们的新根证书加入到若干个信任库中。我们会在未来几周里开始用新中间证书来签发证书,并在社区论坛里发布进一步的公告。

希望我们关于新证书结构的导览是有趣的和干货的。我们期待继续通过一张张的证书来改进互联网隐私。我们由衷感谢 IdenTrust 在早期和后来不间断的支持我们让互联网更安全的愿景。

我们依靠社区和支持者来提供我们的服务。如果你的公司或者机构希望可以赞助 Let’s Encrypt 可以发邮件到 sponsor#letsencrypt.org 。我们需要您力所能及的帮助、

JIRA 内置用户目录与 LDAP 的关系

作为运维开发怎么能不折腾一下 JIRA 呢。JIRA 支持从多个来源获取用户信息,默认的是内置用户目录,一个比较常见需求是改用 LDAP 作为用户目录,特别适合公司里有多个账号体系的情况。

如果想了解其中的细节最好方式还是搭个独立的测试环境,主要是 Jira, OpenLDAP 和 MySQL,可以用一个 docker-compose 搞定。安装和配置部分略去,以下直接给出分析过程:

相关的表

表名 功能
cwd_directory 用户目录
cwd_group 用户组
cwd_user 用户
cwd_membership 用户组 -> 用户

cwd_directory

ID directory_name impl_class directory_type
1 JIRA Internal Directory com.atlassian.crowd.directory.InternalDirectory INTERNAL
10000 My OpenLDAP com.atlassian.crowd.directory.OpenLDAP CONNECTOR

之前看到网上有一条语句改库完成 LDAP 迁移的神操作,这个操作会假设 OpenLDAP 的库 ID 是 10000,果然是艺高人胆大。

cwd_group

ID directory_id group_name active local group_type
10000 1 jira-administrators 1 0 GROUP
10010 1 jira-software-users 1 0 GROUP
10110 1 jira-software-users 1 1 GROUP

cwd_user

ID directory_id username active email_address CREDENTIAL
10000 1 root 1 root@example.com {PKCS5S2}********
10102 10000 foo 1 foo@example.com nopass
10100 1 foo 1 foo@example.com {PKCS5S2}********

迁移之前最大的顾虑就是迁移前的数据能够保存了,这块主要分两部分

  • 各种任务,评论等
  • 在项目里的角色
  • 用户组信息

通过实验发现只要保证 internal 和 ldap 里的 username 一致 1 和 2 就可以得到保留,同样,只要保证 group_name 一致 3 就可以得到保留。

结论

结合表里的数据可得到以下结论:

  • username 和 group_name 在各自的库里都是唯一的
  • 从全局看 username 对应的用户只能有一个,取决于目录的优先级顺序
  • 从全局看 group_name 是不同目录下的用户的集合

Pycharm 里为 Vagrantfile 如何设置语法高亮

最近又开始捣鼓 Vagrant 了,但是在 Pycharm (2020.1.2) 里一直没有语法高亮,明明装了 Vagrant 插件的。

还好网上搜到了这个方法,新建 Vagrantfile.xml 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<filetype binary="false" description="Vagrant Configuration File" name="Vagrantfile">
<highlighting>
<options>
<option name="LINE_COMMENT" value="#" />
<option name="COMMENT_START" value="=begin" />
<option name="COMMENT_END" value="=end" />
<option name="HEX_PREFIX" value="" />
<option name="NUM_POSTFIXES" value="" />
<option name="HAS_BRACES" value="true" />
<option name="HAS_BRACKETS" value="true" />
<option name="HAS_PARENS" value="true" />
<option name="HAS_STRING_ESCAPES" value="true" />
</options>
<keywords keywords="BEGIN;END;begin;break;case;do;else;elsif;end;ensure;for;if;in;next;rescue;retry;then;until;when;while" ignore_case="false" />
<keywords2 keywords="__ENCODING__;__END__;__FILE__;__LINE__" />
<keywords3 keywords="and;false;nil;not;or;true" />
<keywords4 keywords="class;def;module;return;self;super;undef;yield" />
</highlighting>
<extensionMap>
<mapping pattern="Vagrantfile" />
</extensionMap>
</filetype>

貌似得放到某个文件夹下,懒得找了,直接在 IDE 里设置吧:

进入 File > Settings > Editor > File Types 对话框,在 Recognized file types: 里点击 + 号,
添加 Vagrantfile 类型即可,其它的配置参照上面 Vagrantfile.xml 里,

唯一要注意的是 Keywords 部分不是以 ; 分割而是需要一行一个 Keyword

错误

1
__ENCODING__;__END__;__FILE__;__LINE__

正确

1
2
3
4
__ENCODING__
__END__
__FILE__
__LINE__

用这种方法还可为任何类型的文件增加语法高亮,是不是很赞!