<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Hi, I'm Vimiix</title><link>https://www.vimiix.com/</link><description>Recent content on Hi, I'm Vimiix</description><generator>Hugo</generator><language>cn-zh</language><copyright>Vimiix Yao; All rights reserved</copyright><lastBuildDate>Thu, 16 Oct 2025 07:54:28 +0000</lastBuildDate><atom:link href="https://www.vimiix.com/index.xml" rel="self" type="application/rss+xml"/><item><title>Kubernetes APIServer 鉴权机制</title><link>https://www.vimiix.com/posts/2025-10-16-kubernetes-apiserver-authorization-mechanism/</link><pubDate>Thu, 16 Oct 2025 07:54:28 +0000</pubDate><guid>https://www.vimiix.com/posts/2025-10-16-kubernetes-apiserver-authorization-mechanism/</guid><description>&lt;h2 id="1-引言">1. 引言&lt;/h2>
&lt;p>在 Kubernetes 集群中，&lt;a href="./posts/2025-09-30-kubernetes-apiserver-authentication-mechanism">认证（Authentication）&lt;/a>解决了“你是谁”的问题，而鉴权（Authorization）则解决了“你能做什么”的问题。鉴权是 Kubernetes 安全框架的核心支柱之一，它位于认证之后，用于确定一个已经通过身份认证的用户或服务账户（ServiceAccount）是否拥有对特定资源执行特定操作的权限。Kubernetes 采用了一种声明式的、基于属性的访问控制（ABAC）和基于角色的访问控制（RBAC）等模型来实现精细化的权限管理。本文将简单介绍 Kubernetes 的鉴权机制，包括其架构、工作流程、核心模型及详细实现原理。&lt;/p>
&lt;h2 id="2-鉴权架构">2. 鉴权架构&lt;/h2>
&lt;p>Kubernetes API Server 是集群所有操作的唯一入口，它也是鉴权决策的中心。当一个请求通过认证后，API Server 会将该请求传递给鉴权模块进行处理。&lt;/p>
&lt;p>Kubernetes 支持多种鉴权模块，例如：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Node&lt;/strong>： 用于授权 kubelet 对节点和 Pod 等资源的访问。&lt;/li>
&lt;li>&lt;strong>ABAC（Attribute-Based Access Control）&lt;/strong>： 基于属性的访问控制，策略存储在静态文件中。&lt;/li>
&lt;li>&lt;strong>RBAC（Role-Based Access Control）&lt;/strong>： 基于角色的访问控制，是当前最主流和推荐的方式。&lt;/li>
&lt;li>&lt;strong>Webhook&lt;/strong>： 通过调用外部 RESTful 服务来做出鉴权决策。&lt;/li>
&lt;/ul>
&lt;p>这些模块可以同时配置多个。如果配置了多个模块，请求必须被至少一个模块允许，且不能被任何模块拒绝，整个请求才会被授权。API Server 默认允许一个匿名请求，除非被认证层或鉴权层拒绝。&lt;/p>
&lt;p>下图是请求经过认证与鉴权的核心流程：&lt;/p>
&lt;div class="mermaid">
 
flowchart TD
 A[客户端请求&lt;br>（User/ServiceAccount）] --> B[API Server]
 B --> C{认证}
 C -- 携带合法令牌/证书 --> D[认证成功&lt;br>获取请求者身份信息]
 C -- 令牌/证书无效 --> E[认证失败&lt;br>返回401 Unauthorized]
 D --> F[鉴权模块链]
 
 subgraph F [鉴权模块链]
 direction LR
 F1[Node Authorizer] --> F2[ABAC Authorizer] --> F3[RBAC Authorizer] --> F4[Webhook Authorizer]
 end

 F -- 任一模块显式拒绝 --> G[鉴权拒绝&lt;br>返回403 Forbidden]
 F -- 所有模块未通过&lt;br>（包括默认匿名允许） --> H[鉴权拒绝&lt;br>返回403 Forbidden]
 F -- 任一模块允许&lt;br>且无模块拒绝 --> I[鉴权通过]
 
 I --> J[准入控制&lt;br>（Mutating/Validating）]
 J --> K[持久化存储&lt;br>（etcd）]

&lt;/div>
&lt;h2 id="3-鉴权核心流程">3. 鉴权核心流程&lt;/h2>
&lt;p>一个请求的鉴权过程涉及以下几个关键步骤和对象：&lt;/p>
&lt;h3 id="31-请求上下文的构建">3.1. 请求上下文的构建&lt;/h3>
&lt;p>在认证成功后，API Server 会构建一个包含请求者身份和请求详细信息的上下文对象，该对象将作为输入传递给鉴权模块。这个上下文主要包括：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>用户（User）&lt;/strong>： 认证后得到的用户名。&lt;/li>
&lt;li>&lt;strong>用户组（Groups）&lt;/strong>： 用户所属的组。例如，&lt;code>system:authenticated&lt;/code> 组包含所有已认证用户。&lt;/li>
&lt;li>&lt;strong>额外信息（Extra）&lt;/strong>： 包含一些额外的键值对信息，通常由认证器提供。&lt;/li>
&lt;li>&lt;strong>API 请求信息&lt;/strong>：
&lt;ul>
&lt;li>&lt;strong>API Verb&lt;/strong>： 请求的操作类型，如 &lt;code>get&lt;/code>, &lt;code>list&lt;/code>, &lt;code>create&lt;/code>, &lt;code>update&lt;/code>, &lt;code>delete&lt;/code>, &lt;code>patch&lt;/code>, &lt;code>watch&lt;/code>。&lt;/li>
&lt;li>&lt;strong>Resource&lt;/strong>： 请求的 Kubernetes 资源，如 &lt;code>pods&lt;/code>, &lt;code>services&lt;/code>, &lt;code>deployments&lt;/code>。&lt;/li>
&lt;li>&lt;strong>Namespace&lt;/strong>： 如果资源是命名空间范围的，则包含其命名空间。&lt;/li>
&lt;li>&lt;strong>API Group&lt;/strong>： 资源所属的 API 组，如 &lt;code>apps&lt;/code>（对应 &lt;code>deployments&lt;/code>）、&lt;code>”&lt;/code>（核心组，对应 &lt;code>pods&lt;/code>, &lt;code>services&lt;/code>）。&lt;/li>
&lt;li>&lt;strong>子资源（Subresource）&lt;/strong>： 如 &lt;code>pods/exec&lt;/code>, &lt;code>pods/log&lt;/code>, &lt;code>pods/status&lt;/code>。&lt;/li>
&lt;li>&lt;strong>请求路径（Request Path）&lt;/strong>： 对于非资源请求（如 &lt;code>/api&lt;/code>, &lt;code>/healthz&lt;/code>），使用完整路径进行鉴权。&lt;/li>
&lt;li>&lt;strong>资源名称（Resource Name）&lt;/strong>： 某些鉴权模式（如 ABAC）可以支持基于特定资源实例名称的授权。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h3 id="32-多模块决策机制">3.2. 多模块决策机制&lt;/h3>
&lt;p>API Server 会按照配置顺序，依次将请求上下文传递给每个已启用的鉴权模块。每个模块独立做出决策，决策结果有三种：&lt;/p>
&lt;pre tabindex="0">&lt;code>const (
	// DecisionDeny means that an authorizer decided to deny the action.
	DecisionDeny Decision = iota
	// DecisionAllow means that an authorizer decided to allow the action.
	DecisionAllow
	// DecisionNoOpinion means that an authorizer has no opinion on whether
	// to allow or deny an action.
	DecisionNoOpinion
)
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>&lt;strong>拒绝（Deny）&lt;/strong>： 该模块明确拒绝此请求。&lt;/li>
&lt;li>&lt;strong>允许（Allow）&lt;/strong>： 该模块允许此请求。&lt;/li>
&lt;li>&lt;strong>无意见（No Opinion）&lt;/strong>： 该模块对此请求没有匹配的策略，不作决定。&lt;/li>
&lt;/ul>
&lt;p>最终的鉴权结果遵循以下规则：&lt;/p>
&lt;ol>
&lt;li>如果&lt;strong>任何一个&lt;/strong>模块的决策是 &lt;strong>“拒绝”&lt;/strong>，则整个请求被&lt;strong>拒绝&lt;/strong>，API Server 返回 &lt;code>403 Forbidden&lt;/code>。&lt;/li>
&lt;li>如果&lt;strong>至少一个&lt;/strong>模块的决策是 &lt;strong>“允许”&lt;/strong>，并且&lt;strong>没有任何&lt;/strong>模块的决策是 &lt;strong>“拒绝”&lt;/strong>，则整个请求被&lt;strong>允许&lt;/strong>。&lt;/li>
&lt;li>如果&lt;strong>所有&lt;/strong>模块的决策都是 &lt;strong>“无意见”&lt;/strong>，则请求被&lt;strong>拒绝&lt;/strong>。这是一种“默认拒绝”的安全策略。&lt;/li>
&lt;/ol>
&lt;h2 id="4-鉴权模型">4. 鉴权模型&lt;/h2>
&lt;h3 id="41-rbac基于角色的访问控制">4.1. RBAC（基于角色的访问控制）&lt;/h3>
&lt;p>RBAC 是 Kubernetes 中事实上的标准鉴权模型，它使用 &lt;code>Role&lt;/code> / &lt;code>ClusterRole&lt;/code> 和 &lt;code>RoleBinding&lt;/code> / &lt;code>ClusterRoleBinding&lt;/code> 这四种资源来定义和分配权限。&lt;/p>
&lt;h4 id="411-核心资源对象">4.1.1. 核心资源对象&lt;/h4>
&lt;ol>
&lt;li>&lt;strong>Role 与 ClusterRole&lt;/strong>&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>&lt;strong>Role&lt;/strong>： 定义在&lt;strong>特定命名空间&lt;/strong>内的一组权限规则。它指定了可以对哪些资源（如 &lt;code>pods&lt;/code>）执行哪些操作（如 &lt;code>get&lt;/code>, &lt;code>list&lt;/code>, &lt;code>create&lt;/code>）。&lt;/li>
&lt;li>&lt;strong>ClusterRole&lt;/strong>： 定义在&lt;strong>集群范围&lt;/strong>内的一组权限规则。用于定义：
&lt;ul>
&lt;li>集群级别的资源权限（如 &lt;code>nodes&lt;/code>, &lt;code>persistentvolumes&lt;/code>）。&lt;/li>
&lt;li>非资源端点权限（如 &lt;code>/healthz&lt;/code>）。&lt;/li>
&lt;li>跨所有命名空间的命名空间资源权限（需要与 &lt;code>ClusterRoleBinding&lt;/code> 配合）。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Role 示例：&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">apiVersion&lt;/span>: &lt;span style="color:#ae81ff">rbac.authorization.k8s.io/v1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">kind&lt;/span>: &lt;span style="color:#ae81ff">Role&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">metadata&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">namespace&lt;/span>: &lt;span style="color:#ae81ff">default&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">pod-reader&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">rules&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- &lt;span style="color:#f92672">apiGroups&lt;/span>: [&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>] &lt;span style="color:#75715e"># 核心 API 组&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">resources&lt;/span>: [&lt;span style="color:#e6db74">&amp;#34;pods&amp;#34;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">verbs&lt;/span>: [&lt;span style="color:#e6db74">&amp;#34;get&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;list&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;watch&amp;#34;&lt;/span>]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>ClusterRole 示例：&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">apiVersion&lt;/span>: &lt;span style="color:#ae81ff">rbac.authorization.k8s.io/v1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">kind&lt;/span>: &lt;span style="color:#ae81ff">ClusterRole&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">metadata&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># 不需要 &amp;#34;namespace&amp;#34;，因为 ClusterRole 是集群范围的&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">cluster-node-viewer&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">rules&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- &lt;span style="color:#f92672">apiGroups&lt;/span>: [&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">resources&lt;/span>: [&lt;span style="color:#e6db74">&amp;#34;nodes&amp;#34;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">verbs&lt;/span>: [&lt;span style="color:#e6db74">&amp;#34;get&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;list&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;watch&amp;#34;&lt;/span>]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>RoleBinding 与 ClusterRoleBinding&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>RoleBinding&lt;/strong>： 将 &lt;code>Role&lt;/code> 或 &lt;code>ClusterRole&lt;/code> 中定义的权限授予一个或多个用户或服务账户。&lt;code>RoleBinding&lt;/code> 在特定命名空间中生效。如果它引用的是一个 &lt;code>ClusterRole&lt;/code>，那么被授权者只能在 &lt;code>RoleBinding&lt;/code> 所在的命名空间内行使该 &lt;code>ClusterRole&lt;/code> 的权限。&lt;/li>
&lt;li>&lt;strong>ClusterRoleBinding&lt;/strong>： 将 &lt;code>ClusterRole&lt;/code> 中定义的权限授予一个或多个用户或服务账户，授权范围是&lt;strong>整个集群&lt;/strong>。&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>RoleBinding 示例（将 &lt;code>pod-reader&lt;/code> Role 授予用户 &lt;code>vimiix&lt;/code>）：&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">apiVersion&lt;/span>: &lt;span style="color:#ae81ff">rbac.authorization.k8s.io/v1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">kind&lt;/span>: &lt;span style="color:#ae81ff">RoleBinding&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">metadata&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">read-pods&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">namespace&lt;/span>: &lt;span style="color:#ae81ff">default&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">subjects&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- &lt;span style="color:#f92672">kind&lt;/span>: &lt;span style="color:#ae81ff">User&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">vimiix&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">apiGroup&lt;/span>: &lt;span style="color:#ae81ff">rbac.authorization.k8s.io&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">roleRef&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">kind&lt;/span>: &lt;span style="color:#ae81ff">Role&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">pod-reader&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">apiGroup&lt;/span>: &lt;span style="color:#ae81ff">rbac.authorization.k8s.io&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>ClusterRoleBinding 示例（将 &lt;code>cluster-node-viewer&lt;/code> ClusterRole 授予组 &lt;code>system:authenticated&lt;/code>）：&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">apiVersion&lt;/span>: &lt;span style="color:#ae81ff">rbac.authorization.k8s.io/v1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">kind&lt;/span>: &lt;span style="color:#ae81ff">ClusterRoleBinding&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">metadata&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">read-nodes-global&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">subjects&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- &lt;span style="color:#f92672">kind&lt;/span>: &lt;span style="color:#ae81ff">Group&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">system:authenticated&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">apiGroup&lt;/span>: &lt;span style="color:#ae81ff">rbac.authorization.k8s.io&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">roleRef&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">kind&lt;/span>: &lt;span style="color:#ae81ff">ClusterRole&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">cluster-node-viewer&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">apiGroup&lt;/span>: &lt;span style="color:#ae81ff">rbac.authorization.k8s.io&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="412-rbac-鉴权器工作流程">4.1.2. RBAC 鉴权器工作流程&lt;/h4>
&lt;p>RBAC 鉴权器接收到请求上下文后，会执行以下逻辑：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>识别主体（Subject）&lt;/strong>： 从上下文中提取用户和用户组。&lt;/li>
&lt;li>&lt;strong>查找绑定关系&lt;/strong>：
&lt;ul>
&lt;li>列出所有与当前用户及所属用户组匹配的 &lt;code>RoleBinding&lt;/code> 和 &lt;code>ClusterRoleBinding&lt;/code>。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>获取角色规则&lt;/strong>：
&lt;ul>
&lt;li>对于找到的每个 &lt;code>RoleBinding&lt;/code>，获取其引用的 &lt;code>Role&lt;/code> 或 &lt;code>ClusterRole&lt;/code> 中定义的规则。&lt;/li>
&lt;li>对于找到的每个 &lt;code>ClusterRoleBinding&lt;/code>，获取其引用的 &lt;code>ClusterRole&lt;/code> 中定义的规则。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>规则匹配&lt;/strong>：
&lt;ul>
&lt;li>遍历所有收集到的规则，检查是否有任何一条规则与当前请求的 Verb、API Group、Resource、Namespace 和 Resource Name（如果指定）相匹配。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>做出决策&lt;/strong>：
&lt;ul>
&lt;li>如果找到匹配的规则，则返回**“允许”**。&lt;/li>
&lt;li>如果未找到任何匹配的规则，则返回**“无意见”**。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h3 id="42-abac基于属性的访问控制">4.2. ABAC（基于属性的访问控制）&lt;/h3>
&lt;p>ABAC 允许基于资源、用户或请求环境的任意属性组合来定义复杂的策略。策略以每行一个 JSON 对象的形式存储在策略文件（如 &lt;code>--authorization-policy-file=/etc/kubernetes/abac.json&lt;/code>）中。API Server 需要重启才能加载策略文件的变更，这在动态性上不如 RBAC。&lt;/p>
&lt;p>&lt;strong>ABAC 策略文件示例：&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;apiVersion&amp;#34;: &lt;/span>&lt;span style="color:#e6db74">&amp;#34;abac.authorization.kubernetes.io/v1beta1&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;kind&amp;#34;: &lt;/span>&lt;span style="color:#e6db74">&amp;#34;Policy&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;spec&amp;#34;: &lt;/span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;user&amp;#34;: &lt;/span>&lt;span style="color:#e6db74">&amp;#34;alice&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;namespace&amp;#34;: &lt;/span>&lt;span style="color:#e6db74">&amp;#34;project1&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;resource&amp;#34;: &lt;/span>&lt;span style="color:#e6db74">&amp;#34;pods&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;readonly&amp;#34;: &lt;/span>&lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;apiVersion&amp;#34;: &lt;/span>&lt;span style="color:#e6db74">&amp;#34;abac.authorization.kubernetes.io/v1beta1&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;kind&amp;#34;: &lt;/span>&lt;span style="color:#e6db74">&amp;#34;Policy&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;spec&amp;#34;: &lt;/span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;user&amp;#34;: &lt;/span>&lt;span style="color:#e6db74">&amp;#34;kubelet&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;resource&amp;#34;: &lt;/span>&lt;span style="color:#e6db74">&amp;#34;pods&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;readonly&amp;#34;: &lt;/span>&lt;span style="color:#66d9ef">false&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;apiVersion&amp;#34;: &lt;/span>&lt;span style="color:#e6db74">&amp;#34;abac.authorization.kubernetes.io/v1beta1&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;kind&amp;#34;: &lt;/span>&lt;span style="color:#e6db74">&amp;#34;Policy&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;spec&amp;#34;: &lt;/span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;group&amp;#34;: &lt;/span>&lt;span style="color:#e6db74">&amp;#34;system:authenticated&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;readonly&amp;#34;: &lt;/span>&lt;span style="color:#66d9ef">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;nonResourcePath&amp;#34;: &lt;/span>&lt;span style="color:#e6db74">&amp;#34;*&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>ABAC 鉴权器将请求的属性与策略文件中的每条策略进行匹配，如果请求属性是某条策略属性的超集，则该策略匹配，请求被允许。&lt;/p>
&lt;h3 id="43-node-鉴权">4.3. Node 鉴权&lt;/h3>
&lt;p>Node 鉴权是专门授权给 kubelet 访问 Node、Pod、Service、Endpoints 等资源的模块。它自动将 &lt;code>system:nodes&lt;/code> 组中的用户（由 kubelet 的客户端证书中的 &lt;code>CN=system:node:&amp;lt;nodeName&amp;gt;&lt;/code> 标识）与名为 &lt;code>system:node&lt;/code> 的 &lt;code>ClusterRole&lt;/code> 的权限关联起来，该 &lt;code>ClusterRole&lt;/code> 预定义了 kubelet 运行所需的最小权限集。&lt;/p>
&lt;h3 id="44-webhook-鉴权">4.4. Webhook 鉴权&lt;/h3>
&lt;p>Webhook 鉴权将鉴权决策外包给外部 HTTP(S) 服务。当配置了 Webhook 模式（&lt;code>--authorization-webhook-config-file&lt;/code>）后，API Server 会向指定的外部服务发送一个 &lt;code>SubjectAccessReview&lt;/code> 对象，该对象包含了完整的请求上下文。外部服务返回的响应指示是否允许该请求。&lt;/p>
&lt;p>这种方式允许集成企业现有的权限管理系统，例如 Open Policy Agent (OPA)。&lt;/p>
&lt;h2 id="5-最佳实践">5. 最佳实践&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>遵循最小权限原则&lt;/strong>： 只授予执行任务所必需的最小权限。&lt;/li>
&lt;li>&lt;strong>优先使用 RBAC&lt;/strong>： RBAC 资源是声明式的，可以通过 &lt;code>kubectl&lt;/code> 和 YAML 文件轻松管理，无需重启 API Server，是当前的最佳实践。&lt;/li>
&lt;li>&lt;strong>定期审计权限&lt;/strong>： 使用 &lt;code>kubectl auth can-i&lt;/code> 命令或进行定期 RBAC 评审，确保权限分配符合预期。&lt;/li>
&lt;li>&lt;strong>谨慎使用 &lt;code>cluster-admin&lt;/code>&lt;/strong>： &lt;code>cluster-admin&lt;/code> ClusterRole 拥有集群最高权限，应严格控制其分配。&lt;/li>
&lt;li>&lt;strong>利用命名空间进行隔离&lt;/strong>： 为不同的团队或项目创建独立的命名空间，并结合 RBAC 实现逻辑隔离。&lt;/li>
&lt;li>&lt;strong>为服务账户分配明确的权限&lt;/strong>： 运行在 Pod 中的应用程序通过服务账户进行身份认证，务必为其绑定精确的 Role 或 ClusterRole，避免使用默认的 &lt;code>default&lt;/code> 服务账户或过宽的权限。&lt;/li>
&lt;/ol></description></item><item><title>Kubernetes APIServer 认证机制</title><link>https://www.vimiix.com/posts/2025-09-30-kubernetes-apiserver-authentication-mechanism/</link><pubDate>Tue, 30 Sep 2025 08:55:48 +0000</pubDate><guid>https://www.vimiix.com/posts/2025-09-30-kubernetes-apiserver-authentication-mechanism/</guid><description>&lt;p>Kubernetes 作为一个分布式系统，其 API Server 是所有组件交互的核心枢纽。为了保证集群的安全，所有访问 API Server 的请求都必须经过认证（Authentication）、鉴权（Authorization）和准入控制（Admission Control）等步骤。其中，认证是安全的第一道大门，它负责确认请求者的身份。&lt;/p>
&lt;p>Kubernetes 支持多种认证机制（称为 &lt;strong>Authenticator&lt;/strong>），以适应不同的部署环境和安全需求。这些机制可以同时启用，API Server 会逐一尝试，直到有一个机制成功认证该请求。&lt;/p>
&lt;h2 id="一认证流程总览">一、认证流程总览&lt;/h2>
&lt;p>任何访问 API Server 的请求（来自 &lt;code>kubectl&lt;/code>、 Dashboard、 节点上的 kubelet 或其他控制器）都需要携带认证信息。API Server 接收到请求后，会依次尝试配置的认证策略。如果认证成功，请求者的用户名（&lt;code>username&lt;/code>）和用户组（&lt;code>groups&lt;/code>）信息会被嵌入到请求的上下文中，传递给后续的鉴权阶段。如果所有认证策略都失败，则请求以 &lt;code>401 Unauthorized&lt;/code> 错误被拒绝。&lt;/p>
&lt;p>下图描绘了这个通用的认证决策流程：&lt;/p>
&lt;div class="mermaid">
 
flowchart TD
 A[客户端请求&lt;br>携带认证信息] --> B[API Server]
 
 subgraph B [API Server 认证环节]
 direction LR
 C[尝试认证策略 1&lt;br>（如: X509证书）] -->|失败| D[尝试认证策略 2&lt;br>（如: Bearer Token）]
 D -->|失败| E[尝试认证策略 N...&lt;br>（如: ServiceAccount Token）]
 E -->|失败| F[认证失败]
 C -->|成功| G[认证成功]
 D -->|成功| G
 E -->|成功| G
 end

 F --> H[返回 401 Unauthorized]
 G --> I[将身份信息嵌入请求上下文&lt;br>（username, groups, uid等）]
 I --> J[进入鉴权 Authorization 阶段]

&lt;/div>
&lt;h2 id="二各种认证机制">二、各种认证机制&lt;/h2>
&lt;h3 id="1-x509-客户端证书认证-tls">1. X509 客户端证书认证 (TLS)&lt;/h3>
&lt;p>这是集群间组件通信最常用和最安全的机制之一，尤其适用于集群内部组件（如 kubelet、scheduler、controller-manager）与 API Server 的通信。&lt;/p>
&lt;h4 id="原理">原理：&lt;/h4>
&lt;p>API Server 启动时需要配置一个 Certificate Authority (CA) 根证书（通常由 &lt;code>--client-ca-file=SOMEFILE&lt;/code> 参数指定）。客户端需要持有一张由该 CA 签名的客户端证书。在建立 TLS 连接时，客户端会出示此证书。API Server 会验证证书的有效性（是否由信任的 CA 签发、是否在有效期内、CN 和 O 字段等）。&lt;/p>
&lt;p>适用于集群组件身份认证、为外部用户或系统签发长期有效的证书。&lt;/p>
&lt;h4 id="身份信息提取">身份信息提取：&lt;/h4>
&lt;ul>
&lt;li>&lt;strong>用户名 (username)&lt;/strong>： 取自证书的 &lt;code>CN&lt;/code> (Common Name) 字段。&lt;/li>
&lt;li>&lt;strong>用户组 (group)&lt;/strong>： 取自证书的 &lt;code>O&lt;/code> (Organization) 字段。&lt;/li>
&lt;/ul>
&lt;h4 id="时序图">时序图：&lt;/h4>
&lt;div class="mermaid">
 
sequenceDiagram
 participant C as Client (持有客户端证书)
 participant A as API Server&lt;br>(持有CA根证书)

 C->>A: 1. HTTPS Request (Client Hello + 客户端证书)
 Note right of C: 证书由集群CA签发
 A->>A: 2. 使用 --client-ca-file 验证证书
 Note right of A: a. 验证CA签名&lt;br>b. 验证有效期/吊销状态等&lt;br>c. 提取 CN 和 O 字段
 alt 验证成功
 A-->>C: 3. TLS连接建立成功
 A->>A: 4. 认证身份: username=CN, groups=O
 A->>A: 5. 进入鉴权阶段
 else 验证失败
 A-->>C: 3. TLS握手失败 (证书无效)
 end

&lt;/div>
&lt;h3 id="2-静态令牌文件-static-token-file">2. 静态令牌文件 (Static Token File)&lt;/h3>
&lt;p>一种简单的认证方式，通过一个静态文件来管理令牌（Token）。&lt;/p>
&lt;h4 id="原理-1">原理：&lt;/h4>
&lt;p>API Server 通过 &lt;code>--token-auth-file=SOMEFILE&lt;/code> 参数指定一个包含预定义令牌的 CSV 文件。文件格式为：&lt;code>token,username,uid,&amp;quot;group1,group2,group3&amp;quot;&lt;/code>。客户端在请求头中携带 &lt;code>Authorization: Bearer &amp;lt;TOKEN&amp;gt;&lt;/code>。API Server 会在文件中查找匹配的令牌。&lt;/p>
&lt;p>这种认证方式的缺点是，令牌是静态的，修改需要重启 API Server，非常不灵活且不安全。一般用于临时测试或学习，&lt;strong>不推荐在生产环境中使用&lt;/strong>。&lt;/p>
&lt;h3 id="时序图-1">时序图：&lt;/h3>
&lt;div class="mermaid">
 
sequenceDiagram
 participant C as Client
 participant A as API Server&lt;br>(--token-auth-file=tokens.csv)

 C->>A: 1. HTTP Request&lt;br>Authorization: Bearer abc123
 A->>A: 2. 在 tokens.csv 中查找令牌 'abc123'
 alt 找到匹配项
 A->>A: 3. 认证身份: 提取文件中对应用户名和组
 A->>A: 4. 进入鉴权阶段
 else 未找到
 A-->>C: 5. 返回 401 Unauthorized
 end

&lt;/div>
&lt;h3 id="3-serviceaccount-令牌-bearer-tokens">3. ServiceAccount 令牌 (Bearer Tokens)&lt;/h3>
&lt;p>这是 &lt;strong>Kubernetes Pod 内部应用访问 API Server 最主要和推荐的方式&lt;/strong>。&lt;/p>
&lt;h4 id="原理-2">原理：&lt;/h4>
&lt;ol>
&lt;li>&lt;strong>ServiceAccount (SA)&lt;/strong>： 一个命名空间内的资源，代表一个应用身份。&lt;/li>
&lt;li>&lt;strong>Secret&lt;/strong>： 当创建 SA（或由控制器自动创建）时，Kubernetes 会自动生成一个对应的 Secret。该 Secret 包含三个关键信息：&lt;code>ca.crt&lt;/code> (API Server 的 CA)、&lt;code>namespace&lt;/code> (当前命名空间)、&lt;code>token&lt;/code> (一个签名的 JWT 令牌)。&lt;/li>
&lt;li>&lt;strong>令牌挂载&lt;/strong>： Pod 通过 &lt;code>spec.serviceAccountName&lt;/code> 指定 SA，对应的 Secret 会自动挂载到 Pod 内的 &lt;code>/var/run/secrets/kubernetes.io/serviceaccount/&lt;/code>。&lt;/li>
&lt;li>&lt;strong>JWT 验证&lt;/strong>： 客户端（Pod 内的进程）使用此令牌在请求头中设置 &lt;code>Authorization: Bearer &amp;lt;JWT_TOKEN&amp;gt;&lt;/code>。API Server 使用其持有的密钥（通过 &lt;code>--service-account-key-file&lt;/code> 指定，默认为自身 TLS 私钥）来验证 JWT 的签名。&lt;/li>
&lt;/ol>
&lt;h4 id="时序图-2">时序图：&lt;/h4>
&lt;div class="mermaid">
 
sequenceDiagram
 participant P as Pod中的应用程序
 participant A as API Server
 participant S as Secret

 Note over P, S: Pod 启动前
 A->>S: 1. 为 default SA 创建 Secret&lt;br>(包含 token JWT, ca.crt)

 Note over P, A: Pod 运行时
 P->>P: 2. 从挂载卷读取 token 和 ca.crt
 P->>A: 3. HTTPS Request&lt;br>Authorization: Bearer {JWT}
 A->>A: 4. 使用自身密钥验证JWT签名&lt;br>并解析载荷（payload）
 alt JWT有效
 A->>A: 5. 认证身份: system:serviceaccount:{ns}:{sa-name}&lt;br>用户组: system:serviceaccounts
 A->>A: 6. 进入鉴权阶段
 else JWT无效
 A-->>P: 7. 返回 401 Unauthorized
 end

&lt;/div>
&lt;h3 id="4-bootstrap-令牌-bootstrap-tokens">4. Bootstrap 令牌 (Bootstrap Tokens)&lt;/h3>
&lt;p>这是一种为了简化节点加入集群流程而设计的&lt;strong>短期&lt;/strong>令牌机制，是静态令牌的“动态”方案，推荐 worker 节点首次加入集群时使用。&lt;/p>
&lt;h4 id="原理-3">原理：&lt;/h4>
&lt;ol>
&lt;li>&lt;strong>令牌格式&lt;/strong>： 格式为 &lt;code>[a-z0-9]{6}.[a-z0-9]{16}&lt;/code> (例如 &lt;code>abcdef.0123456789abcdef&lt;/code>)。&lt;/li>
&lt;li>&lt;strong>Secret 资源&lt;/strong>： Bootstrap 令牌实际上对应着 &lt;code>kube-system&lt;/code> 命名空间中的一个特定 Secret，其类型为 &lt;code>bootstrap.kubernetes.io/token&lt;/code>。这使得令牌可以通过 API 动态管理，无需重启 API Server。&lt;/li>
&lt;li>&lt;strong>用途&lt;/strong>： 新节点使用此令牌发起 TLS 引导请求，API Server 的 Bootstrap Token Authenticator 会验证 Secret 是否存在且未过期。认证成功后，节点随后会申请正式的证书用于长期通信。&lt;/li>
&lt;/ol>
&lt;h4 id="时序图-3">时序图：&lt;/h4>
&lt;div class="mermaid">
 
sequenceDiagram
 participant N as New Node (新节点)
 participant A as API Server
 participant C as Controller Manager&lt;br>(TokenCleaner)

 N->>A: 1. 证书签名请求(CSR)&lt;br>Authorization: Bearer bootstrap-token-abcdef
 A->>A: 2. 检查 kube-system 中是否存在&lt;br>对应的 bootstrap-token Secret
 alt Secret 存在且有效
 A->>A: 3. 认证成功
 A->>A: 4. 批准CSR（可能需要手动）
 A-->>N: 5. 颁发正式客户端证书
 N->>A: 6. 使用新证书进行后续通信
 C->>C: 7. (稍后) 自动清理过期的bootstrap Secret
 else Secret 无效或不存在
 A-->>N: 8. 返回 401，加入集群失败
 end

&lt;/div>
&lt;h3 id="5-openid-connect-oidc-令牌">5. OpenID Connect (OIDC) 令牌&lt;/h3>
&lt;p>这是一种连接企业现有身份系统（如 Google、Azure AD、Keycloak、Dex 等）的&lt;strong>标准协议&lt;/strong>，用于集成外部用户认证系统。适用于&lt;strong>开发者、管理员等人类用户通过企业单点登录（SSO）系统访问集群&lt;/strong>。&lt;/p>
&lt;h4 id="原理-4">原理：&lt;/h4>
&lt;ol>
&lt;li>&lt;strong>三方协作&lt;/strong>： 涉及客户端（&lt;code>kubectl&lt;/code>）、API Server 和外部身份提供商（IdP）。&lt;/li>
&lt;li>&lt;strong>用户登录&lt;/strong>： 用户首先通过 &lt;code>kubectl&lt;/code> 在 &lt;strong>外部&lt;/strong> IdP 完成认证，获取一个 IdP 签名的 JWT 令牌（ID Token）。&lt;/li>
&lt;li>&lt;strong>传递令牌&lt;/strong>： &lt;code>kubectl&lt;/code> 在请求 API Server 时，在 &lt;code>Authorization&lt;/code> 头中携带此 ID Token。&lt;/li>
&lt;li>&lt;strong>API Server 验证&lt;/strong>： API Server 不直接与 IdP 通信，而是通过配置（&lt;code>--oidc-issuer-url&lt;/code>, &lt;code>--oidc-client-id&lt;/code>）获取 IdP 的公钥，来验证 JWT 令牌的签名。它还会验证令牌的受众（&lt;code>aud&lt;/code> Claim）是否与 &lt;code>--oidc-client-id&lt;/code> 匹配。&lt;/li>
&lt;/ol>
&lt;h4 id="时序图-4">时序图：&lt;/h4>
&lt;div class="mermaid">
 
sequenceDiagram
 participant U as User (kubectl)
 participant A as APIServer
 participant I as OIDC Identity Provider (IdP)

 U->>I: 1. 用户登录 (获取ID Token)
 I-->>U: 2. 返回签名的 JWT ID Token
 U->>A: 3. API Request&lt;br>Authorization: Bearer {IDToken}
 A->>A: 4. 从配置的issuer-url获取JWKS公钥
 A->>A: 5. 使用公钥验证JWT签名&lt;br>并检查audience/issuer/有效期
 alt 验证成功
 A->>A: 6. 认证身份: 提取JWT中的preferred_username/groups等字段
 A->>A: 7. 进入鉴权阶段
 else 验证失败
 A-->>U: 8. 返回 401 Unauthorized
 end

&lt;/div>
&lt;h3 id="6-webhook-令牌认证">6. Webhook 令牌认证&lt;/h3>
&lt;p>这是一种&lt;strong>扩展机制&lt;/strong>，当 Kubernetes 内置的认证方式都无法满足需求时，可以将认证决策委托给一个外部的 RESTful 服务，适用于集成自定义的认证系统、私有身份存储等。&lt;/p>
&lt;h4 id="原理-5">原理：&lt;/h4>
&lt;ol>
&lt;li>&lt;strong>配置&lt;/strong>： API Server 启动时配置 &lt;code>--authentication-token-webhook-config-file&lt;/code>，该文件指向一个外部服务的配置（URL、CA 等）。&lt;/li>
&lt;li>&lt;strong>委托验证&lt;/strong>： 当客户端携带 Bearer Token 请求时，API Server 会将一个 &lt;code>TokenReview&lt;/code> 对象发送给外部 Webhook 服务。&lt;/li>
&lt;li>&lt;strong>外部决策&lt;/strong>： Webhook 服务验证令牌的有效性，并将结果（认证成功与否，以及用户信息）封装在 &lt;code>TokenReview&lt;/code> 对象的 &lt;code>status&lt;/code> 字段中返回。&lt;/li>
&lt;li>&lt;strong>API Server 决策&lt;/strong>： API Server 根据 Webhook 的响应决定是否认证通过。&lt;/li>
&lt;/ol>
&lt;h4 id="时序图-5">时序图：&lt;/h4>
&lt;div class="mermaid">
 
sequenceDiagram
 participant C as Client
 participant A as APIServer
 participant W as External Webhook Service

 C->>A: 1. API Request&lt;br>Authorization: Bearer {TOKEN}
 A->>W: 2. 构造并发送 TokenReview Request&lt;br>{apiVersion: authentication.k8s.io/v1, kind: TokenReview, spec: {token: "TOKEN"}}
 W->>W: 3. 根据自定义逻辑验证Token&lt;br>（查数据库、调用其他API等）
 W-->>A: 4. 返回 TokenReview Response&lt;br>{status: {authenticated: true, user: {username: "foo", groups: ["bar"]}}}
 alt authenticated: true
 A->>A: 5. 认证成功，使用返回的用户信息
 A->>A: 6. 进入鉴权阶段
 else authenticated: false
 A-->>C: 7. 返回 401 Unauthorized
 end

&lt;/div>
&lt;h2 id="三总结对比">三、总结对比&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:center">认证机制&lt;/th>
&lt;th style="text-align:center">主要用途&lt;/th>
&lt;th style="text-align:center">管理方式&lt;/th>
&lt;th style="text-align:center">安全性&lt;/th>
&lt;th style="text-align:center">生产环境推荐&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:center">&lt;strong>X509 证书&lt;/strong>&lt;/td>
&lt;td style="text-align:center">组件间通信，系统级访问&lt;/td>
&lt;td style="text-align:center">动态（CA签发）&lt;/td>
&lt;td style="text-align:center">非常高&lt;/td>
&lt;td style="text-align:center">&lt;strong>推荐&lt;/strong>（用于组件）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">&lt;strong>ServiceAccount&lt;/strong>&lt;/td>
&lt;td style="text-align:center">&lt;strong>Pod内应用&lt;/strong>访问API&lt;/td>
&lt;td style="text-align:center">动态（K8s自动管理）&lt;/td>
&lt;td style="text-align:center">高&lt;/td>
&lt;td style="text-align:center">&lt;strong>必须使用&lt;/strong>（用于Pod）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">&lt;strong>Bootstrap Token&lt;/strong>&lt;/td>
&lt;td style="text-align:center">&lt;strong>新节点加入&lt;/strong>集群&lt;/td>
&lt;td style="text-align:center">动态（Secret资源）&lt;/td>
&lt;td style="text-align:center">中（短期）&lt;/td>
&lt;td style="text-align:center">&lt;strong>推荐&lt;/strong>（用于节点引导）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">&lt;strong>OIDC&lt;/strong>&lt;/td>
&lt;td style="text-align:center">&lt;strong>人类用户&lt;/strong>单点登录&lt;/td>
&lt;td style="text-align:center">外部IdP管理&lt;/td>
&lt;td style="text-align:center">高&lt;/td>
&lt;td style="text-align:center">&lt;strong>推荐&lt;/strong>（用于用户）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">&lt;strong>Webhook&lt;/strong>&lt;/td>
&lt;td style="text-align:center">集成自定义认证系统&lt;/td>
&lt;td style="text-align:center">外部服务管理&lt;/td>
&lt;td style="text-align:center">取决于实现&lt;/td>
&lt;td style="text-align:center">按需使用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">&lt;strong>静态Token文件&lt;/strong>&lt;/td>
&lt;td style="text-align:center">测试&lt;/td>
&lt;td style="text-align:center">静态文件，需重启&lt;/td>
&lt;td style="text-align:center">低&lt;/td>
&lt;td style="text-align:center">&lt;strong>不推荐&lt;/strong>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>理解这些认证机制的原理和适用场景，是构建和维护一个安全、可靠的 Kubernetes 集群的基石。&lt;/p></description></item><item><title>使用 kubeadm 搭建 kubernetes 集群</title><link>https://www.vimiix.com/posts/2024-12-16-deploy-kubernetes-by-kubeadm/</link><pubDate>Mon, 16 Dec 2024 15:23:48 +0000</pubDate><guid>https://www.vimiix.com/posts/2024-12-16-deploy-kubernetes-by-kubeadm/</guid><description>&lt;p>准备4台虚拟机，拓扑为一个 master, 3 个 node 节点，操作系统均为 CentOS 7.9，网络采用桥接模式，设置静态IP。&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>机器名&lt;/th>
&lt;th>IP&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>master&lt;/td>
&lt;td>192.168.3.200&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>node1&lt;/td>
&lt;td>192.168.3.201&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>node2&lt;/td>
&lt;td>192.168.3.202&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>node3&lt;/td>
&lt;td>192.168.3.203&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="安装前环境准备">安装前环境准备&lt;/h2>
&lt;ol>
&lt;li>YUM 源修改&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 1. 修改 yum 源到国内&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 2. 安装部分依赖包&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>yum install -y yum-utils device-mapper-persistent-data lvm2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 3. 增加 docker yum 源&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 4. 增加 kubernetes yum 源&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cat &lt;span style="color:#e6db74">&amp;lt;&amp;lt;EOF &amp;gt; /etc/yum.repos.d/kubernetes.repo
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">[kubernetes]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">name=Kubernetes
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">enabled=1
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">gpgcheck=1
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">repo_gpgcheck=1
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="2">
&lt;li>安装一些常用工具&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>yum install wget jq psmisc vim net-tools telnet git -y
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="3">
&lt;li>关闭防火墙，SELinux，DNSmasq(配置DNS和DHCP的轻量级工具)，swap&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>systemctl disable --now firewalld
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>systemctl disable --now dnsmasq
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>swapoff -a &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> sysctl -w vm.swappiness&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sed -ri &lt;span style="color:#e6db74">&amp;#39;/^[^#]*swap/s@^@#@&amp;#39;&lt;/span> /etc/fstab
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 修改 /etc/selinux/config 文件中的配置为 disabled，然后重启生效&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sed -i &lt;span style="color:#e6db74">&amp;#39;s#SELINUX=enforcing#SELINUX=disabled#g&amp;#39;&lt;/span> /etc/selinux/config
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="4">
&lt;li>安装 ntpdate，同步时钟 (如果已经配置了时钟同步服务器，则跳过)&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>rpm -ivh http://mirrors.wlnmp.com/centos/wlnmp-release-centos.noarch.rpm &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> yum install ntpdate -y &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> echo &lt;span style="color:#e6db74">&amp;#39;Asia/Shanghai&amp;#39;&lt;/span> &amp;gt; /etc/timezone &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> ntpdate time2.aliyun.com
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 设置定时任务每5分钟同步一下时钟&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>crontab -e
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 配置如下规则&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>*/5 * * * * /usr/sbin/ntpdate time2.aliyun.com
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="5">
&lt;li>默认 limit 配置&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>ulimit -SHn &lt;span style="color:#ae81ff">65535&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>vim /etc/security/limits.conf
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 末尾追加如下内容&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>* soft nofile &lt;span style="color:#ae81ff">65536&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>* hard nofile &lt;span style="color:#ae81ff">131072&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>* soft nproc &lt;span style="color:#ae81ff">65535&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>* hard nproc &lt;span style="color:#ae81ff">655350&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>* soft memlock unlimited
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>* hard memlock unlimited
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="6">
&lt;li>在master 生成ssh秘钥，拷贝到其他节点，实现master免密登录其他节点&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>ssh-keygen -t rsa
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">for&lt;/span> i in node1 node2 node3; &lt;span style="color:#66d9ef">do&lt;/span> ssh-copy-id -i .ssh/id_rsa.pub $i; &lt;span style="color:#66d9ef">done&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="安装-k8s-集群">安装 k8s 集群&lt;/h2>
&lt;blockquote>
&lt;p>1.24版本以后默认使用containerd 作为runtime，所以安装 containerd&lt;/p>
&lt;/blockquote>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 修改必要系统配置&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>modprobe br_netfilter
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#ae81ff">1&lt;/span> &amp;gt; /proc/sys/net/bridge/bridge-nf-call-iptables
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#ae81ff">1&lt;/span> &amp;gt; /proc/sys/net/ipv4/ip_forward
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>yum install -y containerd kubelet kubeadm kubectl --disableexcludes&lt;span style="color:#f92672">=&lt;/span>kubernetes
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 启动 containerd&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>systemctl daemon-reload
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>systemctl enable containerd
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>systemctl enable kubelet
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 把 containerd 默认配置写到配置文件中&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>containerd config default &amp;gt; /etc/containerd/config.toml
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 编辑 /etc/containerd/config.toml 文件，&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 如果存在 disabled_plugins = [&amp;#34;cri&amp;#34;]，把列表中的cri 去掉，或者注释掉这一行&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 否则 kubeadm init 的时候会报错 &amp;#34;CRI v1 image API is not implemented for endpoint&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 另外把 containerd 的 cgroup 驱动改为 systemd，找到如下行，把SystemdCgroup 改为true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">[&lt;/span>plugins.&lt;span style="color:#e6db74">&amp;#34;io.containerd.grpc.v1.cri&amp;#34;&lt;/span>.containerd.runtimes.runc.options&lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SystemdCgroup &lt;span style="color:#f92672">=&lt;/span> true
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 国内配置一下国内源，修改配置文件中 config_path 的值&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> config_path &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;/etc/containerd/certs.d&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 保存配置文件退出，然后创建/etc/containerd/certs.d/目录，在这个目录下创建不同站点的代理&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># hosts.toml中可以配置多个镜像仓库，containerd下载镜像时会根据配置的顺序使用镜像仓库，&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 只有当上一个仓库下载失败才会使用下一个镜像仓库。&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 因此，镜像仓库的配置原则就是镜像仓库下载速度越快，那么这个仓库就应该放在最前面。&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># docker hub镜像加速&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir -p /etc/containerd/certs.d/docker.io
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cat &amp;gt; /etc/containerd/certs.d/docker.io/hosts.toml &lt;span style="color:#e6db74">&amp;lt;&amp;lt; EOF
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">server = &amp;#34;https://docker.io&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">[host.&amp;#34;https://dockerproxy.com&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> capabilities = [&amp;#34;pull&amp;#34;, &amp;#34;resolve&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">[host.&amp;#34;https://docker.m.daocloud.io&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> capabilities = [&amp;#34;pull&amp;#34;, &amp;#34;resolve&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">[host.&amp;#34;https://reg-mirror.qiniu.com&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> capabilities = [&amp;#34;pull&amp;#34;, &amp;#34;resolve&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">[host.&amp;#34;https://registry.docker-cn.com&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> capabilities = [&amp;#34;pull&amp;#34;, &amp;#34;resolve&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">[host.&amp;#34;http://hub-mirror.c.163.com&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> capabilities = [&amp;#34;pull&amp;#34;, &amp;#34;resolve&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># registry.k8s.io镜像加速&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir -p /etc/containerd/certs.d/registry.k8s.io
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>tee /etc/containerd/certs.d/registry.k8s.io/hosts.toml &lt;span style="color:#e6db74">&amp;lt;&amp;lt; &amp;#39;EOF&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">server = &amp;#34;https://registry.k8s.io&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">[host.&amp;#34;https://k8s.m.daocloud.io&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> capabilities = [&amp;#34;pull&amp;#34;, &amp;#34;resolve&amp;#34;, &amp;#34;push&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># docker.elastic.co镜像加速&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir -p /etc/containerd/certs.d/docker.elastic.co
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>tee /etc/containerd/certs.d/docker.elastic.co/hosts.toml &lt;span style="color:#e6db74">&amp;lt;&amp;lt; &amp;#39;EOF&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">server = &amp;#34;https://docker.elastic.co&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">[host.&amp;#34;https://elastic.m.daocloud.io&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> capabilities = [&amp;#34;pull&amp;#34;, &amp;#34;resolve&amp;#34;, &amp;#34;push&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># gcr.io镜像加速&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir -p /etc/containerd/certs.d/gcr.io
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>tee /etc/containerd/certs.d/gcr.io/hosts.toml &lt;span style="color:#e6db74">&amp;lt;&amp;lt; &amp;#39;EOF&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">server = &amp;#34;https://gcr.io&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">[host.&amp;#34;https://gcr.m.daocloud.io&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> capabilities = [&amp;#34;pull&amp;#34;, &amp;#34;resolve&amp;#34;, &amp;#34;push&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ghcr.io镜像加速&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir -p /etc/containerd/certs.d/ghcr.io
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>tee /etc/containerd/certs.d/ghcr.io/hosts.toml &lt;span style="color:#e6db74">&amp;lt;&amp;lt; &amp;#39;EOF&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">server = &amp;#34;https://ghcr.io&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">[host.&amp;#34;https://ghcr.m.daocloud.io&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> capabilities = [&amp;#34;pull&amp;#34;, &amp;#34;resolve&amp;#34;, &amp;#34;push&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># k8s.gcr.io镜像加速&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir -p /etc/containerd/certs.d/k8s.gcr.io
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>tee /etc/containerd/certs.d/k8s.gcr.io/hosts.toml &lt;span style="color:#e6db74">&amp;lt;&amp;lt; &amp;#39;EOF&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">server = &amp;#34;https://k8s.gcr.io&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">[host.&amp;#34;https://k8s-gcr.m.daocloud.io&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> capabilities = [&amp;#34;pull&amp;#34;, &amp;#34;resolve&amp;#34;, &amp;#34;push&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># mcr.m.daocloud.io镜像加速&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir -p /etc/containerd/certs.d/mcr.microsoft.com
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>tee /etc/containerd/certs.d/mcr.microsoft.com/hosts.toml &lt;span style="color:#e6db74">&amp;lt;&amp;lt; &amp;#39;EOF&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">server = &amp;#34;https://mcr.microsoft.com&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">[host.&amp;#34;https://mcr.m.daocloud.io&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> capabilities = [&amp;#34;pull&amp;#34;, &amp;#34;resolve&amp;#34;, &amp;#34;push&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># nvcr.io镜像加速&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir -p /etc/containerd/certs.d/nvcr.io
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>tee /etc/containerd/certs.d/nvcr.io/hosts.toml &lt;span style="color:#e6db74">&amp;lt;&amp;lt; &amp;#39;EOF&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">server = &amp;#34;https://nvcr.io&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">[host.&amp;#34;https://nvcr.m.daocloud.io&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> capabilities = [&amp;#34;pull&amp;#34;, &amp;#34;resolve&amp;#34;, &amp;#34;push&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># quay.io镜像加速&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir -p /etc/containerd/certs.d/quay.io
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>tee /etc/containerd/certs.d/quay.io/hosts.toml &lt;span style="color:#e6db74">&amp;lt;&amp;lt; &amp;#39;EOF&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">server = &amp;#34;https://quay.io&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">[host.&amp;#34;https://quay.m.daocloud.io&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> capabilities = [&amp;#34;pull&amp;#34;, &amp;#34;resolve&amp;#34;, &amp;#34;push&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># registry.jujucharms.com镜像加速&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir -p /etc/containerd/certs.d/registry.jujucharms.com
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>tee /etc/containerd/certs.d/registry.jujucharms.com/hosts.toml &lt;span style="color:#e6db74">&amp;lt;&amp;lt; &amp;#39;EOF&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">server = &amp;#34;https://registry.jujucharms.com&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">[host.&amp;#34;https://jujucharms.m.daocloud.io&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> capabilities = [&amp;#34;pull&amp;#34;, &amp;#34;resolve&amp;#34;, &amp;#34;push&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># rocks.canonical.com镜像加速&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir -p /etc/containerd/certs.d/rocks.canonical.com
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>tee /etc/containerd/certs.d/rocks.canonical.com/hosts.toml &lt;span style="color:#e6db74">&amp;lt;&amp;lt; &amp;#39;EOF&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">server = &amp;#34;https://rocks.canonical.com&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">[host.&amp;#34;https://rocks-canonical.m.daocloud.io&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> capabilities = [&amp;#34;pull&amp;#34;, &amp;#34;resolve&amp;#34;, &amp;#34;push&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 重启 containerd 服务&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>systemctl restart containerd
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>为了加快集群部署速度，预先下载kubeadm 所需的全部镜像&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># （optional） 查看 kubeadm 所需的全部镜像&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kubeadm config images list
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 拉去所需镜像&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kubeadm config images pull
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在master 上通过kubeadm一键安装 master节点&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kubeadm init
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在所有 node 节点上执行 &lt;code>kubeadm init&lt;/code> 日志最后的 join 命令&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 这个命令根据实际的输出内容执行&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kubeadm join 192.168.3.200:6443 --token 9unwvu.jogfdub8buf3y2os &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --discovery-token-ca-cert-hash sha256:cad35b01dc5094aa82456e2d166393f5d790e4a452ebbd3df39801cd8997a241
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>设置kubectl 要加载的配置，可以通过把配置文件放到 ~/.kube/config 或者设置 KUBECONFIG 环境变量&lt;/p>
&lt;p>这里我使用环境变量的方式，将设置环境变量的命令放到 ~/.bashrc 里&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># master 节点&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#e6db74">&amp;#39;export KUBECONFIG=/etc/kubernetes/admin.conf&amp;#39;&lt;/span> &amp;gt;&amp;gt; ~/.bashrc
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># node 节点&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#e6db74">&amp;#39;export KUBECONFIG=/etc/kubernetes/kubelet.conf&amp;#39;&lt;/span> &amp;gt;&amp;gt; ~/.bashrc
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>source ~/.bashrc
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 测试&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>root@master ~&lt;span style="color:#f92672">]&lt;/span>&lt;span style="color:#75715e"># kubectl get nodes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>NAME STATUS ROLES AGE VERSION
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>master NotReady control-plane 19m v1.28.2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>node1 NotReady &amp;lt;none&amp;gt; 12m v1.28.2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>node2 NotReady &amp;lt;none&amp;gt; 12m v1.28.2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>node3 NotReady &amp;lt;none&amp;gt; 12m v1.28.2
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="安装--calico-网络插件">安装 calico 网络插件&lt;/h2>
&lt;p>此时所以得节点还是处于 &lt;code>NotReady&lt;/code> 状态，因为还没有安装 CNI 网络插件&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kubectl apply -f &lt;span style="color:#e6db74">&amp;#34;https://docs.projectcalico.org/manifests/calico.yaml&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>(Optional) 安装 nerdctl 用于手动下载镜像&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>wget https://github.com/containerd/nerdctl/releases/download/v2.0.2/nerdctl-2.0.2-linux-amd64.tar.gz
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir -p /usr/local/containerd/bin/ &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> tar -zxvf nerdctl-2.0.2-linux-amd64.tar.gz -C /usr/local/containerd/bin/ &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> ln -s /usr/local/containerd/bin/nerdctl /usr/local/bin/nerdctl
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>如何做code review</title><link>https://www.vimiix.com/posts/2024-09-20-how-to-code-review/</link><pubDate>Fri, 20 Sep 2024 08:54:48 +0000</pubDate><guid>https://www.vimiix.com/posts/2024-09-20-how-to-code-review/</guid><description>&lt;h2 id="为什么要做code-review">为什么要做code review&lt;/h2>
&lt;p>从一个需求开发的完整生命周期来看，大约会包含以下过程:&lt;/p>
&lt;ol>
&lt;li>需求分析，考虑人员情况、功能预期上线时间、技术难度等诸多因素，与 PM 讨论合理调整需求，分配资源；&lt;/li>
&lt;li>整体设计，对设计进行 review；&lt;/li>
&lt;li>代码开发；&lt;/li>
&lt;li>开发自我 review，观察、思考与整体设计的出入，保证代码遵循团队规范（至少得通过静态代码检查）；&lt;/li>
&lt;li>添加相应的单元测试，并保证已有的单元测试不受影响；&lt;/li>
&lt;li>&lt;strong>code review，请相关同事对代码进行 review，修复 review 中发现的问题&lt;/strong>；&lt;/li>
&lt;li>提交代码给 QA 测试，QA 会进行功能测试、集成测试、性能测试、压力测试等；&lt;/li>
&lt;li>修复测试发现的 bug 后功能上线。&lt;/li>
&lt;/ol>
&lt;p>从上属流程可以看出，代码写完了会有专业的测试同事来进行全方位的测试，那么为什么还要 code review 呢？其次，合格的程序员理论上都会 review 自己的代码，还有没有必要请同事来 review 呢？&lt;/p>
&lt;p>一个 bug，可能在代码 review 的时候被发现；也可能在测试的时候被发现；最坏的情况下，被用户发现，当然，此时很可能给用户、产品带来损失。越早发现问题，受影响的人员越少，修复成本就越小。&lt;/p>
&lt;p>开发人员有时也有一个误区，觉得自己完成 “代码开发” 这一步就万事大吉了，测试的事情就应交给 QA 去执行。但很多时候，QA 更多从功能角度去验证代码，有时候黑箱测试也很难覆盖到全部情况，比如异常、安全性问题、版本依赖。况且现在很多公司都逐渐去掉专门的 Test，开发得自己测试，但思维定势导致很难发现自己的问题，对自己代码的 review 也是如此。另外，知道自己的代码也被人“拿着放大镜仔细观察”，自然写代码的时候也认真一些。&lt;/p>
&lt;p>另外，从上述流程可以看出：&lt;/p>
&lt;ul>
&lt;li>其实应该有两次定位不同的 review：对整体设计的 review，主要目的是从大方向上保证设计确实能满足需求；其次是 code review，保证代码实现符合设计约束，且编码没有明显的缺陷。&lt;/li>
&lt;li>用来 code review 的代码，应该既满足团队代码规范，又基本保证功能的正确。&lt;/li>
&lt;/ul>
&lt;p>在我们的实践中，对于复杂的系统，会要求现先设计 review。而对于简单的、或者开发比较有把握的功能，则是将设计 review 与代码 review 合并。本文讨论的 code review 其实就是后者：既关注设计，又关注核心代码。&lt;/p>
&lt;h2 id="code-review-的目标和原则">code review 的目标和原则&lt;/h2>
&lt;p>code review 的首要目标是：&lt;strong>找出代码中的缺陷，保证代码质量&lt;/strong>。其次，通过 review 也能相互学习，提高团队整体水平，而且特别有利于新人快速融入团队，保持与团队风格一致。最后，保证每个核心功能有多个同事了解，降低风险。&lt;/p>
&lt;p>从其核心目标我们就可以看出 code review 的第一原则：&lt;strong>对事不对人&lt;/strong>。这一点需要 reviewer、author 达成共识。code review 不是批斗大会，reviewer、author 并没有高低之分。&lt;/p>
&lt;p>对于 reviewer 而言，参与的首要目的在于协助发现问题，因此：&lt;/p>
&lt;ul>
&lt;li>尽量质疑自己而不是对方&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>✓：我没有看懂这段代码的逻辑
×：你这段代码有问题吧&lt;/p>
&lt;/blockquote>
&lt;ul>
&lt;li>指出代码有问题而不是指责人&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>✓：这段代码是不是可能出问题
×：你又写出 bug 来了&lt;/p>
&lt;/blockquote>
&lt;p>对于 author，应当把 review 当作一次学习成长的机会，毕竟平时又有谁来给你免费建议和指导呢？因此，不要一开始就是防御的姿态，即使 reviewer 有所误解也不用脸红脖子粗的去争论。&lt;/p>
&lt;p>要真正让 author 放松，达到和谐 review 的效果，个人觉得还要注意以下几点：&lt;/p>
&lt;ul>
&lt;li>review 不应该与任何 KPI 挂钩，review 发现的缺陷不应纳入绩效考核。&lt;/li>
&lt;li>团队 Leader 应该少发言（否则就会变成 Leader 说啥就是啥），甚至要少参加无关的 review，毕竟任何人都不希望给 Leader 留下自己很菜的印象。&lt;/li>
&lt;li>得需要有人充当仲裁者的角色，化解 reviewer 与 author 的冲突。而且，必要的时候维护 author。&lt;/li>
&lt;/ul>
&lt;h2 id="code-review-步骤以及注意事项">code review 步骤以及注意事项&lt;/h2>
&lt;p>code review 并不简单是一堆人一起看代码，要让 code review 的效果最大化，整个流程是需要认真组织的：&lt;/p>
&lt;h3 id="review-前">review 前&lt;/h3>
&lt;ul>
&lt;li>author 得保证代码质量：代码满足团队规范且基本测试通过&lt;/li>
&lt;li>author 至少提前一天通知参与 review 会议的同事，提供必要的需求、设计文档&lt;/li>
&lt;li>reviewer 简单了解需求、设计、代码实现&lt;/li>
&lt;/ul>
&lt;p>这里需要注意的是，与会人员越少越好，无关的同事最好不要参加 review。如果与会者对相关的功能不感兴趣，那么就存粹是在浪费时间，这也是很多人不喜欢 code review 的原因。&lt;/p>
&lt;h3 id="review-中">review 中&lt;/h3>
&lt;p>review 的过程大约是：&lt;/p>
&lt;ul>
&lt;li>author 大致讲解需求和整体设计，然后是核心代码&lt;/li>
&lt;li>reviewer 提出问题，author 确认是否是问题&lt;/li>
&lt;li>对发现的问题进行记录，分类处理&lt;/li>
&lt;/ul>
&lt;p>review 中需要注意：&lt;/p>
&lt;ul>
&lt;li>控制 review 的时间，codereview 本身就是一个高强度的活动，时间过长大家都很疲惫，效率也会下降，经验值一个小时左右比较合适。&lt;/li>
&lt;li>控制 review 的代码量，需要大家仔细阅读的代码最好也控制在 200 - 400 逻辑行（这个数值也是 the art of unix programming 中对模块代码量的建议值）&lt;/li>
&lt;li>需要有支主持人控场，把控时间和进度。和任何高效会议一样，应该避免发散，尽量只发现问题，并不深入讨论如何解决问题。&lt;/li>
&lt;li>记录发现的问题，以及相应的解决方案：
&lt;ul>
&lt;li>已有明确修复方案只待执行？&lt;/li>
&lt;li>还是需要修复但解决方法尚需要进一步讨论？&lt;/li>
&lt;li>还是在下一次迭代时再修复？&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>reviewer 内容依次是：&lt;/p>
&lt;ul>
&lt;li>是否满足功能需求，有没有多做，有没有少做，有没有潜在的 bug，&lt;/li>
&lt;li>是否满足非功能需求。性能（高频调用的函数、核心算法）？可用性（异常处理是否完善）？可读性？&lt;/li>
&lt;li>代码质量、规范&lt;/li>
&lt;/ul>
&lt;p>上面也提到，code review 本身是个高强度的活动，因此容易漏掉一些关键点点，因此不妨做一个 review list，对照这个 list 去考察代码。list 保证了 code review 的质量下限，且不影响与会者发挥主观能动性，发现其他问题。&lt;/p>
&lt;p>此外，也需要避免在没有明确规范的问题下过多争执。达到同样的效果，A 方法可以，B 方法也可以，如果团队没有约束用哪种方法，那么就不要在 code review 的时候讨论。&lt;/p>
&lt;h3 id="review-后">review 后&lt;/h3>
&lt;p>review 会议结束并不意味这个 review 这个活动结束，因为还有待解决的问题，因此应该借助工具跟踪 review 发现的问题，指明问题的修复者、问题的验收人、问题的验收时间。当一次 review 所关联的所有问题都得到修复之后，review 活动才算结束。&lt;/p>
&lt;h2 id="总结">总结&lt;/h2>
&lt;p>如何才能 code review 有效且高效，总结以下几点：&lt;/p>
&lt;ul>
&lt;li>会前充分准备，与会人员最少化&lt;/li>
&lt;li>参考 review list，发现问题但不发散去讨论解决问题&lt;/li>
&lt;li>会后跟进问题修复&lt;/li>
&lt;/ul>
&lt;p>最重要的事，是通过几次组织良好的实践让大家看到 code review 确实是有价值的，有所收获才愿意持续付出。&lt;/p>
&lt;blockquote>
&lt;p>本文摘自: &lt;a href="https://www.cnblogs.com/xybaby/p/12601471.html">https://www.cnblogs.com/xybaby/p/12601471.html&lt;/a>, 特此留存学习。&lt;/p>
&lt;/blockquote></description></item><item><title>Python中的弱引用</title><link>https://www.vimiix.com/posts/2024-08-12-weakref-in-python/</link><pubDate>Mon, 12 Aug 2024 08:35:48 +0000</pubDate><guid>https://www.vimiix.com/posts/2024-08-12-weakref-in-python/</guid><description>&lt;h2 id="前言">前言&lt;/h2>
&lt;p>在理解弱引用之前，我们需要先了解 python 中的对象引用和垃圾回收的基本概念，这样更有助于对弱引用的理解。&lt;/p>
&lt;h3 id="对象引用">对象引用&lt;/h3>
&lt;p>在 Python 中，一切皆为对象，包括变量、函数、类以及其实例化的对象。我们要是有对象就必须通过赋值语句来创建一个引用（可以理解为标签），比如 &lt;code>a = 1&lt;/code>，这里 &lt;code>a&lt;/code> 就是对象 &lt;code>1&lt;/code> 的引用。每个引用的所指向的对象都可以通过 &lt;code>id()&lt;/code> 函数来查看，两个引用是否相同可以通过 &lt;code>is&lt;/code> 运算符来比较。&lt;/p>
&lt;p>&lt;em>(&lt;code>==&lt;/code>和 &lt;code>is&lt;/code> 的区别：&lt;code>==&lt;/code> 运算符比较对象的值（对象中保存的数据），而 &lt;code>is&lt;/code> 比较引用的对象是否为同一个。)&lt;/em>&lt;/p>
&lt;p>如果想要查看一个对象的内引用次数，可以通过 &lt;code>sys.getrefcount(a) -1&lt;/code> 查看（&lt;em>减1是因为调用 sys.getrefcount 函数传递参数时会增加一次引用&lt;/em>）。&lt;/p>
&lt;p>&lt;strong>为了说明后面的弱引用，我们将计数的引用成为强引用。&lt;/strong>&lt;/p>
&lt;h2 id="垃圾回收">垃圾回收&lt;/h2>
&lt;p>Python 中的对象在以下两种情况下会被视为垃圾，进而将其所占用的内存回收（GC）：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>没有任何引用指向该对象&lt;/strong>；&lt;/li>
&lt;li>存在循环引用；（&lt;em>循环引用解释器会判定为无法获取，所以都会被销毁，不在本文展开&lt;/em>）&lt;/li>
&lt;/ol>
&lt;p>&lt;code>del&lt;/code> 语法可以实现删除一个对象的引用，而不是对象本身，当我们将指向目标对象的所有引用都删除后，才会触发 Python 的解释器的垃圾回收机制，回收该对象的内存地址。（&lt;strong>理解到这里就够，关于更详细的分代垃圾回收的算法等，请自行学习&lt;/strong>）&lt;/p>
&lt;p>另外，除了使用 &lt;code>del&lt;/code> 来删除引用外，重新绑定也可能会导致对象引用数量归零触发垃圾回收。比如：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>a &lt;span style="color:#f92672">=&lt;/span> [&lt;span style="color:#ae81ff">1&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>a &lt;span style="color:#f92672">=&lt;/span> [&lt;span style="color:#ae81ff">2&lt;/span>] &lt;span style="color:#75715e"># 此时 [1] 这个对象就会被销毁，因为引用 a 被指向了 [2] 这个对象，[1] 的引用数就归零了&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="weakref-模块的原理">weakref 模块的原理&lt;/h2>
&lt;p>在 Python 中，内存管理是一个重要的话题。通过前言的铺垫，我们大概了解了 Python 是通过引用计数的方式实现内存管理机制来管理对象的生命周期的。然而，有些情况下，我们可能希望在不影响对象生命周期的前提下，引用某些对象，这时候就可以使用 Python 中的 &lt;code>weakref&lt;/code> 模块。&lt;/p>
&lt;p>&lt;code>weakref&lt;/code>（弱引用）是一个用于跟踪对象的模块，它允许创建不会阻止对象被垃圾回收的引用。通常情况下，Python 中的强引用会增加对象的引用计数，当强引用计数变为零时，Python的垃圾回收器会自动回收该对象。然而，使用 &lt;code>weakref&lt;/code> 创建的弱引用不会增加对象的引用计数，因此，当该对象的强引用计数降为零时，即使弱引用仍然存在，垃圾回收器也会回收该对象。&lt;/p>
&lt;p>弱引用的存在使得我们可以创建对象的缓存、代理对象等场景，并且不会因不必要的引用而导致内存泄漏或对象无法及时回收。&lt;/p>
&lt;h2 id="weakref-的使用方法">weakref 的使用方法&lt;/h2>
&lt;h3 id="创建弱引用">创建弱引用&lt;/h3>
&lt;p>在 Python 中，&lt;code>weakref.ref&lt;/code> 函数可以用于创建弱引用。例如：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> weakref
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">A&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> __init__(self, value):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>value &lt;span style="color:#f92672">=&lt;/span> value
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>obj &lt;span style="color:#f92672">=&lt;/span> A(&lt;span style="color:#ae81ff">10&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>weak_ref &lt;span style="color:#f92672">=&lt;/span> weakref&lt;span style="color:#f92672">.&lt;/span>ref(obj)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(weak_ref) &lt;span style="color:#75715e"># 输出：&amp;lt;weakref at 0x...; to &amp;#39;A&amp;#39; at 0x...&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 通过弱引用访问原对象&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(weak_ref()) &lt;span style="color:#75715e"># 输出：&amp;lt;__main__.A object at 0x...&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在上述代码中，我们创建了一个 &lt;code>A&lt;/code> 对象并将其传递给 &lt;code>weakref.ref&lt;/code> 以创建一个弱引用。通过 &lt;code>weak_ref()&lt;/code> 可以访问到原始对象。需要注意的是，如果原对象被垃圾回收，&lt;code>weak_ref()&lt;/code>将返回 &lt;code>None&lt;/code>。&lt;/p>
&lt;h3 id="弱引用的回调">弱引用的回调&lt;/h3>
&lt;p>在某些情况下，我们可能希望在弱引用所指向的对象被回收时执行特定的操作。这时，我们可以为弱引用注册一个回调函数。例如：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> weakref
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">A&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> __del__(self):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># 这里演示调用顺序，不建议自己在 __del__ 方法中自定义代码&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;A object is being destroyed&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">on_finalize&lt;/span>(reference):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Object &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>reference&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> is being finalized.&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>obj &lt;span style="color:#f92672">=&lt;/span> A()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>weak_ref &lt;span style="color:#f92672">=&lt;/span> weakref&lt;span style="color:#f92672">.&lt;/span>ref(obj, on_finalize)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 删除强引用，触发垃圾回收&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">del&lt;/span> obj
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 输出：A object is being destroyed&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 输出：Object &amp;lt;weakref at 0x...; dead&amp;gt; is being finalized.&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在这里，我们为 &lt;code>weakref.ref&lt;/code> 传递了一个回调函数 &lt;code>on_finalize&lt;/code>。当对象被垃圾回收时，该回调函数会被触发。&lt;/p>
&lt;h3 id="weakrefweakvaluedictionary">weakref.WeakValueDictionary&lt;/h3>
&lt;p>&lt;code>weakref.WeakValueDictionary&lt;/code> 是 &lt;code>weakref&lt;/code> 模块中常用的一个类，它提供了一个类似于字典的对象，其中的值是通过弱引用进行存储的。使用 &lt;code>WeakValueDictionary&lt;/code> 可以避免缓存中的对象被意外持久化，从而有效地防止内存泄漏。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> weakref
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">A&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> __init__(self, value):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>value &lt;span style="color:#f92672">=&lt;/span> value
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>weak_dict &lt;span style="color:#f92672">=&lt;/span> weakref&lt;span style="color:#f92672">.&lt;/span>WeakValueDictionary()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>obj1 &lt;span style="color:#f92672">=&lt;/span> A(&lt;span style="color:#ae81ff">10&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>obj2 &lt;span style="color:#f92672">=&lt;/span> A(&lt;span style="color:#ae81ff">20&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>weak_dict[&lt;span style="color:#e6db74">&amp;#39;a&amp;#39;&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> obj1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>weak_dict[&lt;span style="color:#e6db74">&amp;#39;b&amp;#39;&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> obj2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(weak_dict[&lt;span style="color:#e6db74">&amp;#39;a&amp;#39;&lt;/span>]) &lt;span style="color:#75715e"># 输出：&amp;lt;__main__.A object at 0x...&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">del&lt;/span> obj1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(weak_dict(&lt;span style="color:#e6db74">&amp;#39;a&amp;#39;&lt;/span>)) &lt;span style="color:#75715e"># 输出：None&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>当 &lt;code>obj1&lt;/code> 被删除时，对应的弱引用也会失效，因此在字典中查询 &lt;code>'a'&lt;/code> 的值会返回&lt;code>None&lt;/code>。&lt;/p>
&lt;h3 id="weakrefweakkeydictionary">weakref.WeakKeyDictionary&lt;/h3>
&lt;p>&lt;code>weakref.WeakKeyDictionary&lt;/code> 与 &lt;code>WeakValueDictionary&lt;/code> 类似，只不过它使用弱引用来存储字典的键，而不是值。这在需要根据对象特性进行缓存或者存储临时数据的场景中非常有用。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> weakref
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">A&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> __init__(self, value):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>value &lt;span style="color:#f92672">=&lt;/span> value
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>obj1 &lt;span style="color:#f92672">=&lt;/span> A(&lt;span style="color:#ae81ff">10&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>obj2 &lt;span style="color:#f92672">=&lt;/span> A(&lt;span style="color:#ae81ff">20&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>weak_dict &lt;span style="color:#f92672">=&lt;/span> weakref&lt;span style="color:#f92672">.&lt;/span>WeakKeyDictionary()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>weak_dict[obj1] &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;First&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>weak_dict[obj2] &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;Second&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(weak_dict[obj1]) &lt;span style="color:#75715e"># 输出：First&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">del&lt;/span> obj1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(list(weak_dict&lt;span style="color:#f92672">.&lt;/span>keys())) &lt;span style="color:#75715e"># 仅输出 [&amp;lt;__main__.A object at 0x...&amp;gt;]，obj1 对应的键已删除&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="weakrefref-和-weakrefproxy-的区别">weakref.ref() 和 weakref.proxy() 的区别&lt;/h3>
&lt;p>&lt;code>weakref.ref()&lt;/code> 和 &lt;code>weakref.proxy()&lt;/code> 都是用于创建对象的弱引用的工具，但它们在行为和应用场景上存在一些区别。&lt;/p>
&lt;p>访问方式：&lt;/p>
&lt;ul>
&lt;li>&lt;code>weakref.ref()&lt;/code> 返回的是一个弱引用对象，你需要调用这个对象才能访问原始对象，并且需要手动处理可能返回None的情况。&lt;/li>
&lt;li>&lt;code>weakref.proxy()&lt;/code> 返回的是一个代理对象，直接使用它就可以像使用原始对象一样操作，但如果原始对象被回收，访问时会抛出异常。&lt;/li>
&lt;/ul>
&lt;p>举个实例便于理解：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> weakref
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">A&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> __init__(self, value):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>value &lt;span style="color:#f92672">=&lt;/span> value
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ref &lt;span style="color:#f92672">=&lt;/span> weakref&lt;span style="color:#f92672">.&lt;/span>ref(c_obj)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ref() &lt;span style="color:#75715e"># &amp;lt;__main__.C object at 0x...&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ref()&lt;span style="color:#f92672">.&lt;/span>value &lt;span style="color:#75715e"># 1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>proxy &lt;span style="color:#f92672">=&lt;/span> weakref&lt;span style="color:#f92672">.&lt;/span>proxy(c_obj)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>proxy &lt;span style="color:#75715e"># &amp;lt;weakproxy at 0x... to C at 0x...&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>proxy&lt;span style="color:#f92672">.&lt;/span>value &lt;span style="color:#75715e"># 1&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="weakref-的约束">weakref 的约束&lt;/h2>
&lt;p>并非所有对象都可以弱引用。支持弱引用的对象包括&lt;strong>类实例、用 Python(但不是用C) 编写的函数、实例方法、set、frozensets、一些文件对象、生成器、类型对象、套接字、数组、deque、正则表达式模式对象和代码对象&lt;/strong>。&lt;/p>
&lt;p>一些内置类型(如 &lt;code>str&lt;/code>，&lt;code>list&lt;/code> 和 &lt;code>dict&lt;/code> )不直接支持弱引用，但可以通过子类化添加支持:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">Dict&lt;/span>(dict):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>obj &lt;span style="color:#f92672">=&lt;/span> Dict(red&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>, green&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">2&lt;/span>, blue&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">3&lt;/span>) &lt;span style="color:#75715e"># 该对象是可以被弱引用的&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>其他内置类型(如 &lt;code>tuple&lt;/code> 和 &lt;code>int&lt;/code> )即使在子类化时也不支持弱引用。&lt;/p>
&lt;h2 id="weakref-的应用场景">weakref 的应用场景&lt;/h2>
&lt;h3 id="实现缓存机制">实现缓存机制&lt;/h3>
&lt;p>在实际应用中，我们经常需要缓存一些对象以提高性能，但又不希望这些对象永久占用内存。这时候可以利用 &lt;code>weakref&lt;/code> 来实现一个弱引用的缓存机制，确保缓存中的对象在内存不足时可以被自动回收。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> weakref
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">Cache&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> __init__(self):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>_cache &lt;span style="color:#f92672">=&lt;/span> weakref&lt;span style="color:#f92672">.&lt;/span>WeakValueDictionary()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">get&lt;/span>(self, key):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>_cache&lt;span style="color:#f92672">.&lt;/span>get(key)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">set&lt;/span>(self, key, value):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>_cache[key] &lt;span style="color:#f92672">=&lt;/span> value
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cache &lt;span style="color:#f92672">=&lt;/span> Cache()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>obj &lt;span style="color:#f92672">=&lt;/span> A(&lt;span style="color:#ae81ff">10&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cache&lt;span style="color:#f92672">.&lt;/span>set(&lt;span style="color:#e6db74">&amp;#39;item&amp;#39;&lt;/span>, obj)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(cache&lt;span style="color:#f92672">.&lt;/span>get(&lt;span style="color:#e6db74">&amp;#39;item&amp;#39;&lt;/span>)) &lt;span style="color:#75715e"># &amp;lt;__main__.A object at 0x...&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">del&lt;/span> obj
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(cache&lt;span style="color:#f92672">.&lt;/span>get(&lt;span style="color:#e6db74">&amp;#39;item&amp;#39;&lt;/span>)) &lt;span style="color:#75715e"># None&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这种机制在需要处理大量临时对象、避免内存泄漏的场景中非常有用。&lt;/p>
&lt;h3 id="使用弱引用集合">使用弱引用集合&lt;/h3>
&lt;p>&lt;code>weakref&lt;/code> 模块还提供了 &lt;code>WeakSet&lt;/code> 类，用于实现一个弱引用的集合。它与普通集合类似，但其中的元素会以弱引用形式存在。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> weakref
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">A&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> __init__(self, value):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>value &lt;span style="color:#f92672">=&lt;/span> value
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>obj1 &lt;span style="color:#f92672">=&lt;/span> A(&lt;span style="color:#ae81ff">10&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>obj2 &lt;span style="color:#f92672">=&lt;/span> A(&lt;span style="color:#ae81ff">20&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>my_set &lt;span style="color:#f92672">=&lt;/span> weakref&lt;span style="color:#f92672">.&lt;/span>WeakSet([obj1, obj2])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(obj1 &lt;span style="color:#f92672">in&lt;/span> my_set) &lt;span style="color:#75715e"># True&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">del&lt;/span> obj1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print(my_set) &lt;span style="color:#75715e"># {&amp;lt;weakref at 0x...; to &amp;#39;A&amp;#39; at 0x...&amp;gt;}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这种弱引用集合在需要跟踪大量对象但又不希望阻止其被回收时特别有用。&lt;/p>
&lt;h2 id="总结">总结&lt;/h2>
&lt;p>&lt;code>weakref&lt;/code> 模块提供了一种有效的内存管理手段，特别适合用于需要临时缓存对象、不希望持久占用内存的场景。通过对 &lt;code>weakref&lt;/code> 的深入理解与合理应用，我们可以在复杂系统中更好地管理对象的生命周期，避免内存泄漏，提高程序的运行效率。&lt;/p></description></item><item><title>自定义 Django 中数据库的后端</title><link>https://www.vimiix.com/posts/2024-07-12-writing-custom-database-backends-in-django/</link><pubDate>Fri, 12 Jul 2024 11:35:48 +0000</pubDate><guid>https://www.vimiix.com/posts/2024-07-12-writing-custom-database-backends-in-django/</guid><description>&lt;p>Django 是一个全能的高级 Python Web 框架，它使得开发人员可以快速构建 Web 应用程序。Django 的一个关键特性就是它对多种数据库的支持。开发人员可以使用各种数据库后端来存储数据，如 SQLite、PostgreSQL、MySQL、Oracle 等等。但是，有时开发人员需要编写自定义数据库后端来支持一些 Django 默认不支持的特定数据库。在本文，我们将学习如何在 Django 中编写自定义数据库的后端。&lt;/p>
&lt;h2 id="步骤1了解django的数据库api">步骤1：了解Django的数据库API&lt;/h2>
&lt;p>在开始编写自定义数据库后端之前，我们需要了解Django的数据库API是如何工作的。Django的数据库API是一组允许开发人员与数据库交互的类和方法。Django数据库API中的关键类是:&lt;/p>
&lt;ul>
&lt;li>&lt;code>django.db.backends.BaseDatabaseWrapper&lt;/code>&lt;/li>
&lt;li>&lt;code>django.db.backends.DatabaseWrapper&lt;/code>&lt;/li>
&lt;li>&lt;code>django.db.backends.BaseDatabaseFeatures&lt;/code>&lt;/li>
&lt;li>&lt;code>django.db.backends.BaseDatabaseOperations&lt;/code>&lt;/li>
&lt;li>&lt;code>django.db.backends.BaseDatabaseClient&lt;/code>&lt;/li>
&lt;li>&lt;code>django.db.backends.BaseDatabaseIntrospection&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>BaseDatabaseWrapper&lt;/code> 类是 Django 中所有数据库后端的基类。它为所有数据库后端提供了一个通用的接口。&lt;code>DatabaseWrapper&lt;/code> 类扩展了 &lt;code>BaseDatabaseWrapper&lt;/code> 类，并为最常见的数据库操作(如执行SQL查询、创建和删除表等)提供了实现。&lt;/p>
&lt;p>&lt;code>BaseDatabaseFeatures&lt;/code>、&lt;code>BaseDatabaseOperations&lt;/code>、&lt;code>BaseDatabaseClient&lt;/code> 和&lt;code>BaseDatabaseIntrospection&lt;/code> 类分别提供了检查数据库特性、执行数据库操作、管理数据库客户端连接和自省数据库模式的方法。&lt;/p>
&lt;h2 id="步骤2创建自定义数据库后端">步骤2：创建自定义数据库后端&lt;/h2>
&lt;p>要在 Django 中创建自定义数据库后端，我们需要创建一个 Python 模块，该模块定义一个类来扩展&lt;code>BaseDatabaseWrapper&lt;/code> 类。类应该实现执行连接数据库和与数据库交互所需操作的方法。&lt;/p>
&lt;p>下面是一个自定义数据库后端连接到一个假设的 NoSQL 数据库的例子:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> django.db.backends.base.base &lt;span style="color:#f92672">import&lt;/span> BaseDatabaseWrapper
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">NoSQLDatabaseWrapper&lt;/span>(BaseDatabaseWrapper):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> vendor &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;NoSQL&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> __init__(self, &lt;span style="color:#f92672">*&lt;/span>args, &lt;span style="color:#f92672">**&lt;/span>kwargs):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> super()&lt;span style="color:#f92672">.&lt;/span>__init__(&lt;span style="color:#f92672">*&lt;/span>args, &lt;span style="color:#f92672">**&lt;/span>kwargs)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>features &lt;span style="color:#f92672">=&lt;/span> NoSQLDatabaseFeatures(self)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>ops &lt;span style="color:#f92672">=&lt;/span> NoSQLDatabaseOperations(self)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>client &lt;span style="color:#f92672">=&lt;/span> NoSQLDatabaseClient(self)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>introspection &lt;span style="color:#f92672">=&lt;/span> NoSQLDatabaseIntrospection(self)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">NoSQLDatabaseFeatures&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> __init__(self, wrapper):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>wrapper &lt;span style="color:#f92672">=&lt;/span> wrapper
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">NoSQLDatabaseOperations&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> __init__(self, wrapper):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>wrapper &lt;span style="color:#f92672">=&lt;/span> wrapper
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">NoSQLDatabaseClient&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> __init__(self, wrapper):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>wrapper &lt;span style="color:#f92672">=&lt;/span> wrapper
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">NoSQLDatabaseIntrospection&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> __init__(self, wrapper):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>wrapper &lt;span style="color:#f92672">=&lt;/span> wrapper
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在本例中，我们定义了一个名为 &lt;code>NoSQLDatabaseWrapper&lt;/code> 的自定义数据库后端。vendor 属性指定此后端支持的数据库供应商的名称。然后我们定义了四个类来扩展 Django 数据库 API 中的基类。每个类都提供了与 NoSQL 数据库交互所需的方法的实现。&lt;/p>
&lt;h2 id="步骤3注册自定义数据库后端">步骤3：注册自定义数据库后端&lt;/h2>
&lt;p>要在 Django 项目中使用自定义数据库后端，我们需要在 Django 的设置中注册它。我们可以通过在 settings 文件中添加以下代码来实现:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>DATABASES &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;default&amp;#39;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;ENGINE&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;path.to.custom.backend.NoSQLDatabaseWrapper&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;NAME&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;database_name&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;HOST&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;localhost&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;PORT&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;1234&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在本例中，我们将自定义后端的名称指定为 &amp;lsquo;ENGINE&amp;rsquo; 键的值。我们还提供必要的连接参数，如数据库的名称、主机名和端口号。&lt;/p>
&lt;p>至此，你已经成功地在 Django 中创建了一个自定义数据库后端。现在，你就可以使用这个后端连接到 NoSQL 数据库并与之交互了。&lt;/p></description></item><item><title>MogDB 中 synchronous_standby_names 参数的工作机制</title><link>https://www.vimiix.com/posts/2024-06-05-working-mechanism-of-synchronous-standby-names-in-mogdb/</link><pubDate>Wed, 05 Jun 2024 11:03:48 +0000</pubDate><guid>https://www.vimiix.com/posts/2024-06-05-working-mechanism-of-synchronous-standby-names-in-mogdb/</guid><description>&lt;p>在 MogDB 中，参数 &lt;code>synchronous_standby_names&lt;/code> 用于配置同步复制设置。特别是当这个参数设置为 &lt;code>'*'&lt;/code> 时，表示可以使用任何一个可用的同步备库作为同步备库。这个配置允许任何一个当前连接的备库都可以被用作同步备库，而不需要明确指定备库的名称。&lt;/p>
&lt;h2 id="工作机制">工作机制&lt;/h2>
&lt;p>当 &lt;code>synchronous_standby_names&lt;/code> 被设置为 &lt;code>'*'&lt;/code> MogDB 的同步复制机制会按照以下步骤选择同步备库：&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>初始连接&lt;/strong>: 当主服务器启动或参数被更改时，主服务器会接受所有连接的备库。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>同步备库的确认&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>主服务器会向所有连接的备库发送同步请求。&lt;/li>
&lt;li>每个备库在接收到同步请求后，会进行确认并向主服务器报告其状态。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>选择同步备库&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>主服务器根据备库的响应情况，选择最早确认的一个或多个备库作为同步备库。&lt;/li>
&lt;li>这个选择过程是动态的，也就是说，当有新的备库连接或当前的同步备库断开连接时，主服务器会重新选择同步备库。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h2 id="优先级和行为">优先级和行为&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>无优先级排序&lt;/strong>: 由于 &lt;code>'*'&lt;/code> 表示任何备库，因此所有备库的优先级是相同的，主服务器只是选择最早响应的备库作为同步备库。&lt;/li>
&lt;li>&lt;strong>动态调整&lt;/strong>: 当一个同步备库断开连接时，主服务器会自动选择下一个响应的备库作为新的同步备库。这保证了同步复制的持续性和可靠性。&lt;/li>
&lt;li>&lt;strong>并发管理&lt;/strong>: 如果多个备库同时连接，主服务器可以处理这些并发连接，并根据同步请求的确认情况选择同步备库。&lt;/li>
&lt;/ul>
&lt;h2 id="示例配置和使用">示例配置和使用&lt;/h2>
&lt;p>假设我们有一个主服务器和三个备库（&lt;code>standby1&lt;/code>、&lt;code>standby2&lt;/code> 和 &lt;code>standby3&lt;/code>），在配置文件 &lt;code>postgresql.conf&lt;/code> 中设置 &lt;code>synchronous_standby_names&lt;/code> 为 &lt;code>'*'&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>synchronous_standby_names = &amp;#39;*&amp;#39;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="场景分析">场景分析&lt;/h3>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>启动时选择&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>当主服务器启动时，所有连接的备库都会发送心跳信号并等待主服务器的同步请求。&lt;/li>
&lt;li>主服务器会选择最早响应的备库作为同步备库。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>运行时变化&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>如果当前同步备库 &lt;code>standby1&lt;/code> 断开连接，主服务器会自动选择下一个响应的备库 &lt;code>standby2&lt;/code> 作为新的同步备库。&lt;/li>
&lt;li>如果新的备库 &lt;code>standby3&lt;/code> 连接到主服务器，且当前没有同步备库，主服务器会选择 &lt;code>standby3&lt;/code> 作为同步备库。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h3 id="动态调整">动态调整&lt;/h3>
&lt;p>如果我们启动了主服务器和三个备库，并且设置了 &lt;code>synchronous_standby_names = '*'&lt;/code>，以下是可能的状态转换示例：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>初始状态&lt;/strong>: 所有备库（&lt;code>standby1&lt;/code>、&lt;code>standby2&lt;/code> 和 &lt;code>standby3&lt;/code>）连接，主服务器选择最早响应的备库 &lt;code>standby1&lt;/code> 作为同步备库。&lt;/li>
&lt;li>&lt;strong>&lt;code>standby1&lt;/code> 断开连接&lt;/strong>: 主服务器自动选择下一个响应的备库 &lt;code>standby2&lt;/code> 作为新的同步备库。&lt;/li>
&lt;li>&lt;strong>新备库连接&lt;/strong>: 新的备库 &lt;code>standby4&lt;/code> 连接，主服务器不会改变当前的同步备库，除非 &lt;code>standby2&lt;/code> 断开连接。&lt;/li>
&lt;/ul>
&lt;h2 id="总结">总结&lt;/h2>
&lt;p>当 &lt;code>synchronous_standby_names&lt;/code> 设置为 &lt;code>'*'&lt;/code> 时，PostgreSQL 会动态地选择任何一个当前可用的备库作为同步备库。这提供了一种灵活和高可用的同步复制机制，无需管理员手动指定备库的名称。选择过程基于备库的响应情况，并在运行时自动调整，以保证同步复制的持续性和可靠性。&lt;/p></description></item><item><title>[译]做一个拥有 Git 好习惯的开发者</title><link>https://www.vimiix.com/posts/2024-02-20-be-a-better-developer-with-these-git-good-practices/</link><pubDate>Tue, 20 Feb 2024 00:50:48 +0000</pubDate><guid>https://www.vimiix.com/posts/2024-02-20-be-a-better-developer-with-these-git-good-practices/</guid><description>&lt;p>如果你是一名开发人员，你可能每天都会使用 git 作为版本控制系统。这个工具的使用对于应用程序的开发过程是至关重要的，无论是在团队协作还是单独工作。但是，常常会遇到混乱的项目库，提交的 commit 信息不明确，不能传达有用的内容，以及滥用分支等问题。了解如何正确使用 git 并遵循良好的实践对于那些想要在就业市场中脱颖而出的人来说是必不可少的。&lt;/p>
&lt;h2 id="git-分支的命名约定">Git 分支的命名约定&lt;/h2>
&lt;p>当我们在处理代码版本控制时，我们应该遵循的一个好的习惯，就是为分支、提交、拉取请求等使用&lt;strong>清晰&lt;/strong>和&lt;strong>描述性&lt;/strong>的名称。确保所有团队成员都有一个简洁的工作流程是至关重要的。除了提高工作效率之外，记录项目的开发过程也简化了团队合作。通过遵循这些实践，你很快就会获益其中。&lt;/p>
&lt;p>基于此，开发者社区创建了一些分支命名的约定，你可以在项目中遵循这些约定。虽然遵循下列规则不是硬性的要求，但它们可以帮助你提高开发技能。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>使用小写字母&lt;/strong>：分支名称不要用大写字母，强制小写;&lt;/li>
&lt;li>&lt;strong>连字符分隔&lt;/strong>：如果分支名称包含多个单词，请使用连字符将它们分开，遵循短横线命名法。避免使用帕斯卡命名法、驼峰命名法或蛇形命名法;&lt;/li>
&lt;li>&lt;strong>(a-z, 0-9)&lt;/strong>：分支名称中只使用字母数字字符和连字符，避免使用其他字符;&lt;/li>
&lt;li>&lt;strong>不要使用连续的连字符(&amp;ndash;)&lt;/strong>：这种做法可能令人困惑。例如，如果你有分支类型(如feature, bugfix, hotfix等)，使用斜杠(/)代替;&lt;/li>
&lt;li>&lt;strong>避免在分支名称的末尾使用连字符&lt;/strong>：这是没有意义的，因为连字符分隔单词，最后没有单词要分隔;&lt;/li>
&lt;li>&lt;strong>最重要的&lt;/strong>：使用描述性的、简洁的、清晰的名称来定义分支上做了什么;&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>不好的分支命名：&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>fixSidebar&lt;/code>&lt;/li>
&lt;li>&lt;code>feature-new-sidebar-&lt;/code>&lt;/li>
&lt;li>&lt;code>FeatureNewSidebar&lt;/code>&lt;/li>
&lt;li>&lt;code>feat_add_sidebar&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>好的分支名：&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>feature/new-sidebar&lt;/li>
&lt;li>add-new-sidebar&lt;/li>
&lt;li>hotfix/interval-query-param-on-get-historical-data&lt;/li>
&lt;/ul>
&lt;h2 id="分支名称约定前缀">分支名称约定前缀&lt;/h2>
&lt;p>有时分支的目的并不明确。它可以是一个新特性、错误修复、文档更新或其他任何东西。为了解决这个问题，通常的做法是在分支名称上使用前缀来快速解释分支的目的。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>feature&lt;/strong>：它表达了一个将被开发的新功能。例如：&lt;code>feature/add-filters&lt;/code>;&lt;/li>
&lt;li>&lt;strong>release&lt;/strong>：用于准备新版本的发布。&lt;code>release/&lt;/code> 前缀通常用于在合并分支主版本的新更新以创建版本之前执行诸如最后修改和修订之类的任务。例如，&lt;code>release/v3.3.1-beta&lt;/code>;&lt;/li>
&lt;li>&lt;strong>bugfix&lt;/strong>：它表达的信息是，你正在解决代码中的一个bug，而且它通常与一个问题有关。例如，&lt;code>bugfix/sign-in-flow&lt;/code>;&lt;/li>
&lt;li>&lt;strong>hotfix&lt;/strong>：类似于 bugfix，但它与修复生产环境中存在的关键错误有关。例如，&lt;code>hotfix/cors-error&lt;/code>;&lt;/li>
&lt;li>&lt;strong>docs&lt;/strong>：写一些文档。例如，&lt;code>docs/quick-start&lt;/code>;&lt;/li>
&lt;/ul>
&lt;p>如果你在工作中使用任务管理相关的工具，如Jira, Trello, ClickUp，或任何类似的工具，可以考虑先创建用户故事卡，每张卡有一个数字相关联。所以，通常可以把这些卡的编号用在分支名称的前缀中。例如：&lt;/p>
&lt;ul>
&lt;li>&lt;code>feature/T-531-add-sidebar&lt;/code>&lt;/li>
&lt;li>&lt;code>docs/T-789-update-readme&lt;/code>&lt;/li>
&lt;li>&lt;code>hotfix/T-142-security-path&lt;/code>&lt;/li>
&lt;/ul>
&lt;h2 id="提交信息-commit-message">提交信息 Commit message&lt;/h2>
&lt;p>接下来，我们来讨论一下提交消息。不幸的是，我们经常会看到这样的提交信息，如：“added a lot of things”或“Pikachu, I choose you”等（是的，我曾经发现一个项目的提交信息与poksammon战斗有关）。&lt;/p>
&lt;p>提交信息在开发过程中非常重要，创造一段美好的历史会在你的人生旅途中给你带来很多帮助。和分支一样，社区也有对于提交信息的规范约定，你可以在下面了解到:&lt;/p>
&lt;ul>
&lt;li>提交消息有三个重要部分:&lt;strong>主题 Subject&lt;/strong>、&lt;strong>描述 Description&lt;/strong>和&lt;strong>页脚 Footer&lt;/strong>。提交的主题是必需的，并且定义了提交的目的。描述(主体)用于为提交的目的提供额外的上下文和解释。最后是页脚，通常用于元数据，如分配提交。虽然同时使用描述和页脚被认为是一种很好的做法，但这不是必需的。&lt;/li>
&lt;li>&lt;strong>在主题行中使用祈使句&lt;/strong>。 例如：&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>Add README.md ✅;
Added README.md ❌;
Adding README.md ❌;&lt;/p>
&lt;/blockquote>
&lt;ul>
&lt;li>&lt;strong>主题行的第一个字母大写&lt;/strong>。 例如：&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>Add user authentication ✅;
add user authentication ❌;&lt;/p>
&lt;/blockquote>
&lt;ul>
&lt;li>&lt;strong>不要以句号结束主题行&lt;/strong>。例如&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>Update unit tests ✅;
Update unit tests. ❌;&lt;/p>
&lt;/blockquote>
&lt;ul>
&lt;li>将主题行限制在&lt;strong>50个字符&lt;/strong>以内，也就是说，要清晰简洁;&lt;/li>
&lt;li>正文以&lt;strong>72个字符&lt;/strong>进行换行，并&lt;strong>将主题与空白行分开&lt;/strong>;&lt;/li>
&lt;li>如果你的正文有多个段落，那么&lt;strong>用空行分隔它们&lt;/strong>;&lt;/li>
&lt;li>如有必要，使用&lt;strong>要点&lt;/strong>而不是段落;&lt;/li>
&lt;/ul>
&lt;h2 id="提交规范">提交规范&lt;/h2>
&lt;blockquote>
&lt;p>&amp;ldquo;The Conventional Commits specification is a lightweight convention on top of commit messages. It provides an easy set of rules for creating an explicit commit history.&amp;rdquo;&lt;/p>
&lt;/blockquote>
&lt;p>以下引用来自 Conventional Commit 的官方网站。该规范是社区中最常用的提交消息的约定。&lt;/p>
&lt;h3 id="结构">结构&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>&amp;lt;type&amp;gt;[optional scope]: &amp;lt;description&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[optional body]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[optional footer(s)]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="提交类型-commit-type">提交类型 Commit type&lt;/h3>
&lt;p>我们要学习的第一个结构是提交类型。它提供了一个清晰的上下文，说明在这个提交中做了什么。下面你可以看到提交类型的列表以及何时使用它们:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>feat&lt;/strong>：引入新功能;&lt;/li>
&lt;li>&lt;strong>fix&lt;/strong>：修正软件错误;&lt;/li>
&lt;li>&lt;strong>refactor&lt;/strong>：用于代码重构，但保留其整体功能;&lt;/li>
&lt;li>&lt;strong>chore&lt;/strong>：不影响生产代码的更新，包括工具、配置或库调整;&lt;/li>
&lt;li>&lt;strong>docs&lt;/strong>：对文档文件的补充或修改;&lt;/li>
&lt;li>&lt;strong>perf&lt;/strong>：提高性能的代码更改;&lt;/li>
&lt;li>&lt;strong>style&lt;/strong>：与代码表现形式相关的调整，如格式和空白;&lt;/li>
&lt;li>&lt;strong>test&lt;/strong>：包含或修正测试;&lt;/li>
&lt;li>&lt;strong>build&lt;/strong>：影响构建系统或外部依赖的修改;&lt;/li>
&lt;li>&lt;strong>ci&lt;/strong>：更改 CI 配置文件和脚本;&lt;/li>
&lt;li>&lt;strong>env&lt;/strong>：调整或添加 CI 过程中的配置文件，例如容器配置参数。&lt;/li>
&lt;/ul>
&lt;h3 id="作用域-scope">作用域 Scope&lt;/h3>
&lt;p>作用域是一个可选的结构，添加到提交类型之后，以提供额外的上下文信息，例如:&lt;/p>
&lt;ul>
&lt;li>&lt;code>fix(ui): resolve issue with button alignment&lt;/code>&lt;/li>
&lt;li>&lt;code>feat(auth): implement user authentication&lt;/code>&lt;/li>
&lt;/ul>
&lt;h3 id="主体-body">主体 Body&lt;/h3>
&lt;p>主体提供了有关提交所引入的更改的详细解释，它通常添加在主题行后面的空白行之后。&lt;/p>
&lt;p>示例：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>Add new functionality to handle user authentication.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>This commit introduces a new module to manage user authentication. It includes
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>functions for user login, registration, and password recovery.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="页脚-footer">页脚 Footer&lt;/h3>
&lt;p>页脚用于提供与提交相关的附加信息。这可以包括诸如谁审查或批准变更之类的细节。&lt;/p>
&lt;p>示例：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>Signed-off-by: John &amp;lt;john.doe@example.com&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Reviewed-by: Anthony &amp;lt;anthony@example.com&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="破坏性变更-breaking-change">破坏性变更 Breaking Change&lt;/h3>
&lt;p>表明提交包含可能导致兼容性问题或需要修改相关代码的重大更改。您可以在页脚添加 &lt;code>BREAKING CHANGE&lt;/code> 或在类型/作用域之后包含 &lt;code>!&lt;/code> 。&lt;/p>
&lt;h3 id="使用提交规范的提交示例">使用提交规范的提交示例&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>chore: add commitlint and husky
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>chore(eslint): enforce the use of double quotes in JSX
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>refactor: type refactoring
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>feat: add axios and data handling
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>feat(page/home): create next routing
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>chore!: drop support for Node 18
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="包含主题正文和页脚的示例">包含主题、正文和页脚的示例&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>feat: add function to convert colors in hexadecimal to rgba
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Lorem Ipsum is simply dummy text of the printing and typesetting industry.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Lorem Ipsum has been the industry&amp;#39;s standard dummy text ever since the 1500s.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Reviewed-by: 2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Refs: #345
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="参考文章">参考文章&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.conventionalcommits.org">https://www.conventionalcommits.org&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://medium.com/@abhay.pixolo/naming-conventions-for-git-branches-a-cheatsheet-8549feca2534">https://medium.com/@abhay.pixolo/naming-conventions-for-git-branches-a-cheatsheet-8549feca2534&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://se-education.org/guides/conventions/git.html">https://se-education.org/guides/conventions/git.html&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://cbea.ms/git-commit/">https://cbea.ms/git-commit/&lt;/a> &lt;a href="https://blog.geekhunter.com.br/o-que-e-commit-e-como-usar-commits-semanticos/">https://blog.geekhunter.com.br/o-que-e-commit-e-como-usar-commits-semanticos/&lt;/a>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>&lt;em>翻译自：&lt;a href="https://dev.to/anthonyvii/be-a-better-developer-with-these-git-good-practices-2dim">https://dev.to/anthonyvii/be-a-better-developer-with-these-git-good-practices-2dim&lt;/a>&lt;/em>&lt;/p></description></item><item><title>SSX，一个有记忆的 ssh 客户端</title><link>https://www.vimiix.com/posts/2023-12-15-ssx/</link><pubDate>Fri, 15 Dec 2023 00:50:48 +0000</pubDate><guid>https://www.vimiix.com/posts/2023-12-15-ssx/</guid><description>&lt;h2 id="需求来源">需求来源&lt;/h2>
&lt;p>对于一个后端程序员来说，在工作中免不了要和繁杂的服务器打交道，ssh 是不可或缺的开发工具。但每次登录都需要输入密码的行为，对于认为一切皆可自动化的程序员来说，肯定是有点繁琐的（如果您是使用图形化界面的用户可忽略）。&lt;/p>
&lt;p>所以我在前段时间考虑，我应该自己实现一个 ssh 客户端，它不需要拥有许多复杂的功能，只需要满足我以下这几个需求即可满足日常使用：&lt;/p>
&lt;ul>
&lt;li>和 ssh 保持差不多的使用习惯&lt;/li>
&lt;li>仅在第一次登录时询问我密码，后续使用无需再提供密码&lt;/li>
&lt;li>可以给服务器它任意的标签，这样我就可以自由地通过IP 或者标签来登录&lt;/li>
&lt;/ul>
&lt;p>于是乎，近期我在业余时间就设计并编写了 &lt;a href="https://github.com/vimiix/ssx">ssx&lt;/a> 这个轻量级的具有记忆的 ssh 客户端。它完美的实现了上面我所需要的功能，也已经被我愉快的应用到了日常的开发中。&lt;/p>
&lt;h2 id="使用方式">使用方式&lt;/h2>
&lt;p>下面就简单介绍一下 ssx 的使用方式。&lt;/p>
&lt;p>ssx 是通过 golang 开发的一个独立的二进制文件，安装方式就是从 &lt;a href="https://github.com/vimiix/ssx/releases">release&lt;/a> 页面下载对应平台的软件包，解压后把 ssx 二进制放到系统的任意目录下，这里我习惯放到 &lt;code>/usr/local/bin&lt;/code> 目录下，如果你选择其他目录下，需要确保存放的目录添加到 $PATH 环境变量中，这样后续使用我们就不用再添加路径前缀，直接通过 ssx 命令就可以运行了。&lt;/p>
&lt;h3 id="登录服务器">登录服务器&lt;/h3>
&lt;p>使用 ssx 登录服务器的时候，基本和 ssh 使用习惯一致，下面是基本命令模式：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>ssx &lt;span style="color:#f92672">[&lt;/span>-s&lt;span style="color:#f92672">]&lt;/span> &lt;span style="color:#f92672">[&lt;/span>USER@&lt;span style="color:#f92672">]&lt;/span>HOST&lt;span style="color:#f92672">[&lt;/span>:PORT&lt;span style="color:#f92672">]&lt;/span> &lt;span style="color:#f92672">[&lt;/span>-k IDENTITY_FILE&lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在这个命令中，&lt;code>USER&lt;/code> 是可以省略的，如果省略则是系统当前用户名；&lt;code>PORT&lt;/code> 是可以省略的，默认是 22，&lt;code>-k IDENTITY_FILE&lt;/code> 代表如果是使用私钥登录，则通过 &lt;code>-k&lt;/code> 来指定私钥的路径，也是可以省略的，默认是 &lt;code>~/.ssh/id_rsa&lt;/code>，当然了，前提是这个文件存在。所以最精简的登录命令就是：&lt;code>ssx &amp;lt;ip&amp;gt;&lt;/code>&lt;/p>
&lt;p>当首次登录，不存在可用私钥时，会通过交互方式来让用户输入密码，一旦登录成功，这个密码就会被 ssx 保存到本地的数据文件中 (默认为 &lt;code>~/.ssx/db&lt;/code>， 可通过环境变量 &lt;code>SSX_DB_PATH&lt;/code> 进行自定义)，下次登录时，仍然执行 &lt;code>ssx &amp;lt;ip&amp;gt;&lt;/code> 即可自动登录。&lt;/p>
&lt;p>注意，登录过的服务器，再次登录时，我嫌输入全部 IP 比较麻烦，所以 ssx 支持输入 IP 中的部分字符，自动搜索匹配进行登录。&lt;/p>
&lt;h3 id="为服务器打标签">为服务器打标签&lt;/h3>
&lt;p>当我们成功登录过一次服务器后，就可以通过 &lt;code>ssx list&lt;/code> 命令来查看目前 ssx 存储的所有服务器列表。下面是一个列表示例：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># output example&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Entries (stored in ssx)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ID | Address | Tags&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#-----+----------------------+--------------------------&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 1 | root@172.23.1.84:22 | centos&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>ssx 会给每个服务器分配一个唯一的 ID，我们在打标签时就需要通过 ID 来指定服务器条目。&lt;/p>
&lt;blockquote>
&lt;p>当然，既然服务器有唯一 ID，ssx 也支持通过 ID 来进行登录：&lt;code>ssx -i &amp;lt;ID&amp;gt;&lt;/code>&lt;/p>
&lt;/blockquote>
&lt;p>打标签需要通过 ssx 的子命令 &lt;code>tag&lt;/code> 来完成，下面是 tag 命令的模式：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>ssx tag -i &amp;lt;ENTRY_ID&amp;gt; &lt;span style="color:#f92672">[&lt;/span>-t TAG1 &lt;span style="color:#f92672">[&lt;/span>-t TAG2 ...&lt;span style="color:#f92672">]]&lt;/span> &lt;span style="color:#f92672">[&lt;/span>-d TAG3 &lt;span style="color:#f92672">[&lt;/span>-d TAG4 ...&lt;span style="color:#f92672">]]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;code>-i&lt;/code> 指定 list 命令输出的要操作的服务器对应的 ID 字段&lt;/li>
&lt;li>&lt;code>-t&lt;/code> 指定要添加的标签名，可以多次指定就可以同时添加多个标签&lt;/li>
&lt;li>&lt;code>-d&lt;/code> 指定要删除的标签名，同样也可以多次指定&lt;/li>
&lt;/ul>
&lt;p>当我们完成对服务器的打标签后，比如上面示例中的服务器，我增加了一个 &lt;code>centos&lt;/code> 的标签，那么我此时就可以通过标签来进行登录了：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>// -t 可省略
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ssx &lt;span style="color:#f92672">[&lt;/span>-t&lt;span style="color:#f92672">]&lt;/span> centos
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="删除服务器记录">删除服务器记录&lt;/h3>
&lt;p>ssx 也支持删除服务器记录，命令如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>ssx delete -i &amp;lt;ID&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>一旦删除，ssx 不会保留该服务器的任何信息，所以下次登录时会等同于新的服务器来对待，ID 也会重新生成。&lt;/p>
&lt;h3 id="目前支持的环境变量">目前支持的环境变量&lt;/h3>
&lt;p>除了上面提到的 &lt;code>SSX_DB_PATH&lt;/code> 可以指定存储数据的文件外，ssx 还支持：&lt;/p>
&lt;ul>
&lt;li>&lt;code>SSX_CONNECT_TIMEOUT&lt;/code>：SSH连接超时时间，默认为 &lt;code>10s&lt;/code>&lt;/li>
&lt;li>&lt;code>SSX_IMPORT_SSH_CONFIG&lt;/code>：是否引用用户 ssh 配置，默认为空&lt;/li>
&lt;/ul>
&lt;p>这里我解释一下 &lt;code>SSX_IMPORT_SSH_CONFIG&lt;/code> 的作用，这个环境变量不设置时，ssx 默认是不会读取用户的 &lt;code>~/.ssh/config&lt;/code> 文件的，ssx 只使用自己存储文件进行检索。如果将这个环境变量设置为非空（任意字符串），ssx 就会在初始化的时候加载用户 ssh 配置文件中存在的服务器条目，&lt;strong>但 ssx 仅读取用于检索和登录，并不会将这些条目持久化到 ssx 的存储文件中&lt;/strong>，所以，如果 ssx IP 登录时，这个 IP 是 &lt;code>~/.ssh/config&lt;/code> 文件中已经配置过登录验证方式的服务器，ssx 匹配到就直接登录了。但 &lt;code>ssx list&lt;/code> 查看时，该服务器会被显示到 &lt;code>found in ssh config&lt;/code> 的表格中，这个表格中的条目是不具有 ID 属性的，以下是一个示例：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>export SSX_IMPORT_SSH_CONFIG&lt;span style="color:#f92672">=&lt;/span>true
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ssx list
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># output example&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Entries (stored in ssx)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># ID | Address | Tags&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#-----+----------------------+--------------------------&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 1 | root@172.23.1.84:22 | centos&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Entries (found in ssh config)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Address | Tags&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># -----------------------------------+----------------------------&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># git@ssh.github.com:22 | github.com&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="操作演示">操作演示&lt;/h3>
&lt;p>最后通过一个演示动画来感受一下这个小可爱的功能吧。&lt;/p>
&lt;p>
&lt;figure>
 &lt;img src="https://raw.githubusercontent.com/vimiix/ssx/master/static/demo.svg?sanitize=true" alt="demo" />
&lt;/figure>


&lt;/p></description></item><item><title>psycopg2基于openGauss中execPramasBatch和execPreparedBatch接口测试</title><link>https://www.vimiix.com/posts/2023-08-04-psycopg2-batch-api-base-on-opengauss-api/</link><pubDate>Fri, 04 Aug 2023 21:50:48 +0000</pubDate><guid>https://www.vimiix.com/posts/2023-08-04-psycopg2-batch-api-base-on-opengauss-api/</guid><description>&lt;blockquote>
&lt;p>如果要查看 Psycopg2 不同接口之间批量操作对比测试，请访问&lt;a href="https://www.vimiix.com/posts/2023-07-12-psycopg2-batch-api-benchmark/">这篇笔记&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;h2 id="测试环境">测试环境&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>组件&lt;/th>
&lt;th>说明&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>客户端操作系统&lt;/td>
&lt;td>Rocky Linux 8&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>服务端配置&lt;/td>
&lt;td>2C6G, 40G HDD&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>CPU&lt;/td>
&lt;td>Intel Xeon Processor (Icelake)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>数据库&lt;/td>
&lt;td>MogDB 5.0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>网络&lt;/td>
&lt;td>300M宽带&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Python&lt;/td>
&lt;td>3.6.8&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Psycopg2&lt;/td>
&lt;td>&lt;a href="https://gitee.com/vimiix/openGauss-connector-python-psycopg2/tree/dev">vimiix/openGauss-connector-python-psycopg2&lt;/a>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;blockquote>
&lt;p>注意：&lt;/p>
&lt;ol>
&lt;li>由于真正的性能和服务器的配置，网络情况相关性也比较大，本测试所有的测试用例环境条件一致，只有参数作为变量，所以请不要注重数值本身，重点关注不同情况下的性能比例&lt;/li>
&lt;li>本测试只取了 100/1000/10000 这个page_size，具有一定的性能趋势，但不代表一味的增大 page_size 就可以提高性能，必然存在一个性能拐点的参数值，而且不同的场景存在不同的性能拐点，要找到性能拐点仍需根据实际情况进行更多的测试。&lt;/li>
&lt;/ol>
&lt;/blockquote>
&lt;h2 id="插入-insert">插入 INSERT&lt;/h2>
&lt;h3 id="execute_values">execute_values&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>rows\page_size&lt;/th>
&lt;th>100(default)&lt;/th>
&lt;th>1000&lt;/th>
&lt;th>10000&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>10,000&lt;/td>
&lt;td>2089&lt;/td>
&lt;td>349&lt;/td>
&lt;td>204&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>50,000&lt;/td>
&lt;td>10842&lt;/td>
&lt;td>1801&lt;/td>
&lt;td>707&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>100,000&lt;/td>
&lt;td>21445&lt;/td>
&lt;td>3625&lt;/td>
&lt;td>1257&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>
&lt;figure>
 &lt;img src="https://static.vimiix.com/upic/2024-09-20/iNwbLv.png" alt="image.png" />
&lt;/figure>


&lt;/p>
&lt;h3 id="execute_params_batch">execute_params_batch&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>rows\page_size&lt;/th>
&lt;th>100(default)&lt;/th>
&lt;th>1000&lt;/th>
&lt;th>10000&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>10,000&lt;/td>
&lt;td>2243&lt;/td>
&lt;td>504&lt;/td>
&lt;td>312&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>50,000&lt;/td>
&lt;td>11574&lt;/td>
&lt;td>2591&lt;/td>
&lt;td>1539&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>100,000&lt;/td>
&lt;td>22552&lt;/td>
&lt;td>5511&lt;/td>
&lt;td>2980&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>
&lt;figure>
 &lt;img src="https://static.vimiix.com/upic/2024-09-20/748CoC.png" alt="image.png" />
&lt;/figure>


&lt;/p>
&lt;h3 id="execute_prepared_batch">execute_prepared_batch&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>rows\page_size&lt;/th>
&lt;th>100(default)&lt;/th>
&lt;th>1000&lt;/th>
&lt;th>10000&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>10,000&lt;/td>
&lt;td>2328&lt;/td>
&lt;td>506&lt;/td>
&lt;td>339&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>50,000&lt;/td>
&lt;td>11307&lt;/td>
&lt;td>2836&lt;/td>
&lt;td>1480&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>100,000&lt;/td>
&lt;td>22976&lt;/td>
&lt;td>5920&lt;/td>
&lt;td>3425&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>
&lt;figure>
 &lt;img src="https://static.vimiix.com/upic/2024-09-20/yeQRc8.png" alt="image.png" />
&lt;/figure>


&lt;/p>
&lt;h3 id="相同行数时不同-page_size-的接口性能对比">相同行数时，不同 page_size 的接口性能对比&lt;/h3>
&lt;p>
&lt;figure>
 &lt;img src="https://static.vimiix.com/upic/2024-09-20/OQynhS.png" alt="image.png" />
&lt;/figure>




&lt;figure>
 &lt;img src="https://static.vimiix.com/upic/2024-09-20/PXguIq.png" alt="image.png" />
&lt;/figure>


&lt;/p>
&lt;h2 id="更新-update">更新 UPDATE&lt;/h2>
&lt;h3 id="execute_values-1">execute_values&lt;/h3>
&lt;blockquote>
&lt;p>用法比较饶，需要把 values 的部分单独用一个 %s 占位&lt;/p>
&lt;/blockquote>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>rows\page_size&lt;/th>
&lt;th>100(default)&lt;/th>
&lt;th>1000&lt;/th>
&lt;th>10000&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>10,000&lt;/td>
&lt;td>3884&lt;/td>
&lt;td>562&lt;/td>
&lt;td>393&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>50,000&lt;/td>
&lt;td>15367&lt;/td>
&lt;td>3429&lt;/td>
&lt;td>2601&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>100,000&lt;/td>
&lt;td>25882&lt;/td>
&lt;td>11313&lt;/td>
&lt;td>5356&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>
&lt;figure>
 &lt;img src="https://static.vimiix.com/upic/2024-09-20/roeISI.png" alt="image.png" />
&lt;/figure>


&lt;/p>
&lt;h3 id="execute_params_batch-1">execute_params_batch&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>rows\page_size&lt;/th>
&lt;th>100(default)&lt;/th>
&lt;th>1000&lt;/th>
&lt;th>10000&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>10,000&lt;/td>
&lt;td>2746&lt;/td>
&lt;td>810&lt;/td>
&lt;td>579&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>50,000&lt;/td>
&lt;td>13359&lt;/td>
&lt;td>4478&lt;/td>
&lt;td>3026&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>100,000&lt;/td>
&lt;td>28432&lt;/td>
&lt;td>8529&lt;/td>
&lt;td>5923&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>
&lt;figure>
 &lt;img src="https://static.vimiix.com/upic/2024-09-20/iPGUHF.png" alt="image.png" />
&lt;/figure>


&lt;/p>
&lt;h3 id="execute_prepared_batch-1">execute_prepared_batch&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>rows\page_size&lt;/th>
&lt;th>100(default)&lt;/th>
&lt;th>1000&lt;/th>
&lt;th>10000&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>10,000&lt;/td>
&lt;td>2747&lt;/td>
&lt;td>896&lt;/td>
&lt;td>635&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>50,000&lt;/td>
&lt;td>12860&lt;/td>
&lt;td>6153&lt;/td>
&lt;td>3626&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>100,000&lt;/td>
&lt;td>28445&lt;/td>
&lt;td>10103&lt;/td>
&lt;td>10828&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>
&lt;figure>
 &lt;img src="https://static.vimiix.com/upic/2024-09-20/c64pUD.png" alt="image.png" />
&lt;/figure>


&lt;/p>
&lt;h2 id="相同行数时不同-page_size-的接口性能对比-1">相同行数时，不同 page_size 的接口性能对比&lt;/h2>
&lt;p>
&lt;figure>
 &lt;img src="https://static.vimiix.com/upic/2024-09-20/pFengW.png" alt="image.png" />
&lt;/figure>




&lt;figure>
 &lt;img src="https://static.vimiix.com/upic/2024-09-20/cWneQ2.png" alt="image.png" />
&lt;/figure>


&lt;/p>
&lt;h2 id="结论">结论&lt;/h2>
&lt;ol>
&lt;li>任何接口，在对于大批量操作时，调大 page_size 均可以得到有效的性能提升&lt;/li>
&lt;li>从数据来看， execute_values 的性能不管在INSERT还是UPDATE的表现，都稍稍好于 execute_params_batch 和 execute_prepared_batch（由于测试的局限性，可能也不一定）。execute_params_batch 和 execute_prepared_batch 两个接口性能差别不大，底层走的是一套接口。&lt;/li>
&lt;li>从使用上来说，execute_values 使用比较饶，如果要使用这个接口更新，写SQL的复杂度大于其他两个，比如相同的更新操作：
&lt;ul>
&lt;li>execute_values： &lt;code>UPDATE t3 as t SET j = data.j FROM (VALUES %s) AS data (j, i) WHERE t.i = data.i&lt;/code>&lt;/li>
&lt;li>其他两个：&lt;code>UPDATE t3 SET j=$1 WHERE t3.i=$2&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol></description></item></channel></rss>