Helm vs Kustomize 深度比较

Helm和Kustomize都是流行的Kubernetes集群部署管理工具,本文比较了两者的优缺点,方便读者根据项目实际情况采用适合的方案。原文: Helm vs Kustomize: why, when, and how[1]

alt
挑战

开始讨论之前,先来看看为什么要使用 Helm 或 Kustomize。

这么多环境,这么多 YAML 文件!

Kubernetes 帮助我们非常容易的为不同用例创建不同的环境,可以在同一个集群甚至多个集群上使用命名空间,可以托管开发、测试、QA、UAT、预发、生产……等等不同的环境。但问题是:如何管理所有这些环境?

第一种也是最直接的方法是创建相同manifest的副本,并为每个副本命名。也就是说,把源文件复制粘贴到每个环境上。

仅使用 kubectl 管理多个环境
仅使用 kubectl 管理多个环境

对于只需对每个环境做出极少改动的简单项目,上述方法可能很适用。例如,除了镜像外,所有 YAML 清单都完全相同。可以打开每个目录中的 deployment.yaml 文件进行更改,保存后运行 kubectl apply -f .,就大功告成了。

然而,在大多数情况下,环境之间的差异并不那么简单。请看下面的例子:

  • 开发环境通过某些容器命令参数进行调试,而这些参数在 QA 或生产环境中不可用。
  • QA 部署了一些边车,用于运行测试,开发和生产环境不具备这种能力。
  • 出于显而易见的原因,生产环境的 RBAC 比其他两个环境的限制性更强。

还有其他更多的可能性:

  • 应用变得越来越大,需要其他依赖服务。例如,MySQL 后端和 Redis 缓存服务器。每个服务都有自己的清单、配置设置和环境差异。
  • 需要实施 CI/CD 流水线,将应用程序(连同其依赖项)测试、构建和部署到多个环境中。

如你所见,单独使用 kubectl 会变成一场噩梦,这就是我们开始探索更高级工具(特指 Helm 和 Kustomize)的原因。让我们先来探讨一下它们各自是如何应对上述挑战的。

Helm

作为 Kubernetes 的包管理器,Helm 提供了一种以"图表(charts)"形式打包、分发和管理应用程序的方法。Helm chart由模板(template)和值(value)文件集合组成,其中模板定义 Kubernetes 资源(如Deployment、Service、ConfigMap),值文件允许自定义模板值。

这样就可以拥有一组模板,为在不同部署(或环境)中发生变化的参数提供占位符。例如,下面是一个 Helm 部署模板,它从值文件中获取副本数量、镜像名称和标签、容器端口和容器启动参数:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-deployment
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ .Release.Name }}
  template:
    metadata:
      labels:
        app: {{ .Release.Name }}
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
          ports:
            - containerPort: {{ .Values.containerPort }}
          args:
            - {{ .Values.startupArguments }}

{{}} 之间的内容都是动态的。也就是说,在chart部署时,它们会被实际值取代。相应的值文件如下所示:

replicaCount: 3

image:
  repository: myapp/image
  tag: v1.0.0

containerPort: 8080

startupArguments: arg1 arg2 arg3

注意: .Release.Name.Chart.Name 变量取自 Chart.yaml,可视为另一个参数来源,用于为集群中的 Kubernetes 组件赋予唯一的名称,这样我们就能在同一个集群中部署同一chart的多个版本。

当 Helm 应用于集群时,Kubernetes API 服务器会收到这些信息:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: myapp
          image: myapp/image:v1.0.0
          ports:
            - containerPort: 8080
          args:
            - arg1
            - arg2
            - arg3

这样就可以为每种环境/用例设置不同的值文件。

对于整个环境的更改,只需修改一次源模板。而对于特定环境的更改,可以应用每个环境对应的值文件。

Helm 使用模板和值构建 YAML 清单
Helm 使用模板和值构建 YAML 清单
Kustomize

Kustomize 的目标是一样的,但不使用模板。相反,它在一个目录中保留完整版本的 YAML 文件。按照惯例,这个文件被称为 base,但也可以根据自己的喜好给它命名。然后可以为每个环境/场景/用例创建一个目录(或目录树),每个目录都需要一个名为 kustomization.yaml 的 YAML 文件,该文件的目的是告知 Kustomize 应该考虑哪些 manifest 文件,以及需要对这些文件进行哪些修改。下面通过例子来说明这,看看如何使用 Kustomize 得出与 Helm 相同的结果。

首先创建一个目录结构:

myapp/
├── kustomization.yaml
├── base
│   └── deployment.yaml
└── overlay
    └── deployment.yaml

myapp/kustomization.yaml 的内容如下:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - base/deployment.yaml

patchesStrategicMerge:
  - overlay/deployment.yaml

base/deployment.yaml 看起来像这样:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: myapp
          image: myapp/image:v1.0.0
          ports:
            - containerPort: 8080

请注意,这是一个完全有效的 YAML,如果需要,也可以按原样应用。

要更改该部署以适应环境需求,可以使用 overlay/deployment.yaml 文件:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment
spec:
  template:
    spec:
      containers:
        - name: myapp
          args:
            - arg1
            - arg2
            - arg3

这样,发送到 Kubernetes API 服务器的文件就变成了

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: myapp
          image: myapp/image:v1.0.0
          ports:
            - containerPort: 8080
          args:
            - arg1
            - arg2
            - arg3

把同样的机制应用到三个环境中,目录结构可以是这样的:

myapp/
├── kustomization.yaml
├── base
│   └── deployment.yaml
├── overlays
│   ├── dev
│   │   └── kustomization.yaml
│   ├── qa
│   │   └── kustomization.yaml
│   └── prod
│       └── kustomization.yaml
└── patches
    └── deployment-patch.yaml

如果需要对整个环境进行更改,只需在 base/deployment 文件中进行一次更改,就会传播到所有地方。针对特定环境的更改在相应环境的自定义文件中完成。

Kustomize 使用补丁和覆盖层,同时保持源 YAML 不变
Kustomize 使用补丁和覆盖层,同时保持源 YAML 不变

现在我们知道了每种工具是如何应对挑战的,下面来看看其优缺点。

alt
第1回合:安装和设置

需要在服务器上安装 Helm,请参阅Five ways to install Helm[2]

虽然可以从 https://kubectl.docs.kubernetes.io/installation/kustomize/ 下载单独的 Kustomize 软件包,但从 1.14 版开始,就已经与 kubectl 捆绑在一起了。因此,除非你的系统中没有(或不需要)kubectl,否则只需运行 kubectl -k 即可调用 Kustomize。

优胜者:Kustomize

第2回合:软件包管理

由于 Helm 顾名思义是软件包管理器,它提供的软件仓库可以搜索和下载特定版本的chart,也可以在同一集群中同时安装多个版本的chart。Kustomize 不会将文件打包成可部署的单元,不过我们可以通过 Kustomize 手动实现同样的效果(Git 发布是其中一种选择)。不过,Helm 提供了开箱即用的功能。

优胜者:Helm

第3回合:模板化能力

Helm 完全依赖 Go 模板,此外还从 Sprig库[3]中借用了一些函数,使模板功能更加多样化。Kustomize 完全不使用模板,而是在将 YAML 清单应用到集群之前,使用overlay和patch对其进行即时修改。

Go 是一种成熟的编程语言,提供了强大的文本操作技术。例如

  • 循环和条件式,如( range)和条件式( ifelsewith),这在生成重复资源或根据用户提供的值进行决策时非常有用。
  • 模板功能通过 Sprig 库实现,该库提供了各种功能,如 defaultpickomittrimupperlowerquote等。

而 Kustomize 却无法做到这一点。不过,它也有一些小技巧。例如

  • ConfigMaps 和 Secrets 的生成器。这些都是声明式指定的,Kustomize 会在构建最终 YAML 时生成资源。
  • Variants(变体):Kustomize 使用overlay层来管理同一应用程序的不同变体,这有助于管理不同的环境(开发、预发、生产)。
  • 用于更新资源字段的转换器(transformers)。常见的转换器包括为资源名称添加前缀/后缀、更新标签和注释以及更新命名空间。转换器可以有选择的应用于不同的资源,从而提供高度的控制。

优胜者:不定(取决于所追求的定制化程度)

第4回合:调试

很明显,在将 YAML 文件应用到群集之前,需要测试这些文件是否存在错误。YAML 使用空格和缩进来定义对象、列表和其他组件,一个不正确的缩进可能会毁掉整个部署。Helm 和 Kustomize 都允许我们在将 YAML 清单应用到群集之前就"查看"这些清单。

Kustomize 有build命令,在将所有patch、overlay、转换器(transformers)等应用到一个包含整个有效负载的大文件后,会生成最终的清单。不过,也可以运行 kubectl apply -k --dry-run 来依赖 API 服务器验证 YAML 清单。

Helm有几种方法可以做同样的事情:

可以使用 helm template 在 YAML 清单发送到 API 服务器之前对其进行渲染,还可以使用 helm lint 根据最佳实践检查chart。

使用 helm install --dry-run (或 helm upgrade )还可以针对 API 服务器测试清单。也就是说,即使 YAML 在语法上是正确的,API 服务器也可能因为其他原因而拒绝接受(例如,缺少 CRD 或接入控制器)。Helm 允许我们在将有效负载应用到 Kubernetes 之前捕获这些错误,从而避免卸载和重新安装有问题的chart。

优胜者:不定

第5回合:版本控制和回滚

如前所述,Helm 能够同时在同一集群中部署同一chart的多个版本。Helm 将部署版本称为revision(修订版),并保留了部署到群集的revision版本历史记录,允许我们在需要时回滚到之前的revision版本。虽然 Kustomize 也可以做同样的事情,但过程复杂且容易出错。

优胜者:Helm

第6回合:Secrets管理

许多情况下,我们需要存储一些敏感信息,作为应用程序部署的一部分。比如 API 密钥、用户凭证、令牌等。在所有情况下,Kubernetes 都提供了 Secret 对象,可以在其中保存机密信息。让我们看看每个工具是如何处理 Secret 创建的:

Helm

将隐私数据存储在 values.yaml 文件中,并使用 b64enc 函数在Secret YAML 清单中将其即时转换为 base64。例如

# values.yaml
database:
  username: admin
  password: secret

以及

# templates/secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
data:
  username: {{ .Values.database.username | b64enc }}
  password: {{ .Values.database.password | b64enc }}

由此产生的 YAML 可以是这样的

---
# Source: my-chart/templates/secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
data:
  username: YWRtaW4=
  password: c2VjcmV0

这里的问题显而易见: 需要将 Values 文件(其中包含纯文本证书)提交到版本控制中。一个可行的解决方案是创建单独的 Values 文件来存储敏感信息,并通过将其添加到 .gitignore 文件来避免将其包含在 git 仓库中。不过这样需要管理多个Values文件,又增加了复杂性。

Kustomize

可以使用 Kustomize secretGenerator 自动从纯文件创建Secret YAML。例如,可以创建如下凭证文件:

# Create the secret file
echo -n 'admin' > ./username.txt
echo -n 'secret' > ./password.txt

Kustomization 文件看起来会像这样:

# kustomization.yaml
secretGenerator:
- name: db-secret
  files:
  - username.txt
  - password.txt

由此产生的清单将是

apiVersion: v1
kind: Secret
metadata:
  name: db-secret-8h5h97g6k8
type: Opaque
data:
  username.txt: YWRtaW4=
  password.txt: c2VjcmV0

虽然 username.txtpassword.txt 也会被添加到 .gitignore,但除非想修改凭据,否则无需在每次部署时都重新创建它们(在运行 git clonegit pull 后)。

显然,用 Base64 存储敏感信息和使用纯文本是一样的,因为 Base64 是一种编码格式,而不是加密方法。也就是说,任何人都可以使用命令行工具将 Base64 字符串转换为原始格式。因此,最佳实践要求我们对secret数据进行加密。Helm 和 Kustomize 都可以使用第三方插件实现这一功能。

例如,如果使用 Kustomize,可以使用 kustomize-secret-generator[4] 插件,它能让你从 Google Cloud Secret Manager、AWS Secrets Manager 或 HashiCorp 获取secret。这样做的目的是将secret以加密形式存储在其中某个支持的平台中。需要时,用户可以依靠插件获取secret、解密并将其应用于群集。下面演示了 Kustomize 如何利用 HashiCorp 的 Vault 实现这一功能:

# kustomization.yaml
secretGenerator:
- name: db-secret
  kvSources:
  - pluginType: vault
    name: my-vault
    namespace: default
    path: secret/data/my-service
    key: db-password

虽然 Helm 有 Helm-Secrets[5] 插件,但不提供从其他平台获取secret的本地支持。相反,它使用 Mozilla SOPS 进行加密。密钥本身可以存储在各种密钥管理系统中,如 AWS KMS、GCP KMS、Azure Key Vault 和 PGP。例如

helm secrets enc secrets.yaml

上述命令对 Secret 模板进行了静态加密,然后可以直接提交到 Git。当我们在另一台机器上重新获取时,需要先解密,然后再将其应用到 Kubernetes:

helm secrets dec secrets.yaml

优胜者:Kustomize

第7回合:处理超大型应用程序

如果应用程序有数百个清单,包含数千行内容,那么使用 Helm 模板处理这些清单很快就会变得力不从心,这里 Kustomize 可能是更好的选择。

例如著名的基于 Kubernetes 的机器学习平台 Kubeflow[6],正在使用 Kustomize 作为部署工具。原因是该平台过于庞大,而且有许多依赖项需要按特定顺序部署。为了解释的更清楚,这是需要部署的资源的一个子集(我们甚至还没有考虑patch或overlay):

用于部署 Kuebflow 的 Kustomization 文件示例摘录
用于部署 Kuebflow 的 Kustomization 文件示例摘录

优胜者:Kustomize

第8回合:与 CI/CD 工具集成

Helm 已被广泛采用,被许多 CI/CD 工具所支持。对 Kustomize 的支持也在增加,但并不广泛。

优胜者:Helm

第9回合(最后一轮):次级组成部分和依赖关系

Helm 内置支持依赖关系处理。如果chart需要一些先决条件(数据库、缓存服务器、OAuth 服务等),可以轻松的在 Chart.yaml 文件中将它们添加为dependencies(依赖项)。Helm 将确保在运行主chart前下载并提供这些先决条件,并且可以选择所需版本。而 Kustomize 则完全由用户手动处理。

优胜者:Helm

获胜者是

alt

Helm!

不过,这里没有输赢之分。每种工具都有自己的优缺点,完全取决于项目的目标、规模、需要部署的环境数量以及复杂程度。这场"对决"的目的只是展示这两种工具之间的区别,而不是鼓吹其中一种优于另一种。

尽管如此,许多项目事实上会在同一个代码库中同时使用这两种工具。不过,本文篇幅过长,无法讨论 Kustomize 的这一功能。不过,可以通过以下链接查看文档:https://github.com/kubernetes-sigs/kustomize/blob/master/examples/chart.md

结论

Helm 和 Kustomize 的目标是一致的:以 DevOps 的方式更轻松的部署包含许多相互依赖的 YAML 清单的大型应用程序。不过,每种工具都有其优于其他工具的用例。在本文中,我们试图让这两种工具面对面比较,看看它们的优缺点。在下一个项目中选择使用 Helm 还是 Kustomize 很大程度上取决于多个因素,但我们希望本文能帮助你做出正确的决定。

https://www.udemy.com/course/helm-the-kubernetes-package-manager-hands-on-course/?couponCode=MEDIUM
https://www.udemy.com/course/helm-the-kubernetes-package-manager-hands-on-course/?couponCode=MEDIUM

希望这篇关于 Helm 和 Kustomize 的文章对你有所帮助。如果深入了解并精通 Helm,强烈推荐 Udemy 上的课程:Helm - Kubernetes 包管理器实践[7]。该课程适合各种水平的学习者,包含大量实践示例和精辟技巧。


你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。为了方便大家以后能第一时间看到文章,请朋友们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!

参考资料
[1]

Helm vs Kustomize: why, when, and how: https://ahmedelfakharany.com/helm-vs-kustomize-why-when-and-how-5d5ba0f80234

[2]

Five ways to install Helm: https://ahmedelfakharany.com/five-ways-to-install-helm-32233ee019a2

[3]

Sprig Library: http://masterminds.github.io/sprig

[4]

kustomize-secret-generator: https://github.com/kubernetes-sigs/kustomize/blob/master/examples/secretGeneratorPlugin.md

[5]

Helm-Secrets: https://github.com/jkroepke/helm-secrets

[6]

Kubeflow: https://github.com/kubeflow/manifests/blob/master/example/kustomization.yaml

[7]

Helm - Kubernetes 包管理器实践: https://www.udemy.com/course/helm-the-kubernetes-package-manager-hands-on-course/?couponCode=MEDIUM

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2810200.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

AI数字人SadTalker实战

1.概述 AI数字人在营销和品牌推广中扮演着至关重要的角色,许多企业和个人正积极利用数字技术来打造属于自己的财富。有没有一种简单而免费的方式来创建自己的数字人呢?本篇博客笔者将为大家介绍如何搭建属于自己的AI数字人。 2.内容 2.1 什么是SadTalker…

Windows部署WebDAV服务并映射到本地盘符实现公网访问本地存储文件

文章目录 前言1. 安装IIS必要WebDav组件2. 客户端测试3. 使用cpolar内网穿透,将WebDav服务暴露在公网3.1 安装cpolar内网穿透3.2 配置WebDav公网访问地址 4. 映射本地盘符访问 前言 在Windows上如何搭建WebDav,并且结合cpolar的内网穿透工具实现在公网访…

【DL】深度学习之语音识别

目录 1 核心概念 2 安装依赖库 3 实践 语音信号处理(Speech Signal Processing)简称语音处理。 语音识别(ASR)和自然语言处理(NLP):语音识别就是将语音信号转化成文字文本,简单实…

解决启动服务报./nginx -s reload nginx: [emerg] unknown directive “错误

重启服务报错 bug: ./nginx -s reload nginx: [emerg] unknown directive "? 原因: 一、可能打开没有关闭 二、刚刚编辑的没成功,乱码了,格式问题,重新配置

汇标网系统搭建,让知识产权保护更智能、更便捷!

据国家商标局统计发布,2023年四季度我国商标申请量达到了6,988,704件,有效商标注册量为44,047,071件! 商标作为企业的重要资产,现在不仅企业、个体户、个人等等,都想在商标市场分得一杯羹。 目前,国家和社会…

多模态表征中的里程碑—CLIP及中文版Chinese-CLIP:理论揭秘、代码微调与论文阅读 (视觉与语义的奇妙共舞~)

我之前一直在使用CLIP/Chinese-CLIP,但并未进行过系统的疏导。这次正好可以详细解释一下。相比于CLIP模型,Chinese-CLIP更适合我们的应用和微调,因为原始的CLIP模型只支持英文,对于我们的中文应用来说不够友好。Chinese-CLIP很好地…

【深度学习笔记】深度学习训练技巧

深度学习训练技巧 1 优化器 随机梯度下降及动量 随机梯度下降算法对每批数据 ( X ( i ) , t ( i ) ) (X^{(i)},t^{(i)}) (X(i),t(i)) 进行优化 g ∇ θ J ( θ ; x ( i ) , t ( i ) ) θ θ − η g g\nabla_\theta J(\theta;x^{(i)},t^{(i)})\\ \theta \theta -\eta g g…

leetcode:491.递增子序列

1.误区:不能直接对数组排序再求解子集,因为那样就改变了原有数组的顺序 2.树形结构:一个一个取数,然后保证是递增序列,且不能重复。(数层上不可以重复取,树枝上可以重复取)收集的结…

[蜥蜴书Chapter2] -- 下载和加载数据

目录 一、下载和加载数据的函数代码 二、代码说明 1、urllib.request.urlretrieve 2、extractall 三、如何调用函数 四、查看数据的结构 1、head函数: 2、info函数: 3、describe函数: 4、绘制柱状图: 一、下载和加载数据…

迁移学习帮大忙!成都理工大学搭建 SCDUNet++ 模型进行滑坡测绘

滑坡是最常见的自然灾害之一,通常由地震和降雨引发,会造成严重的财产损失和人员伤亡。由地震触发的山体滑坡所造成的破坏,有时会比地震本身造成的破坏更为严重。大型地震发生之后,快速、准确地开展滑坡测绘工作 (landslide mappin…

ABAP - Function ALV 05 添加选择框列、全选、取消全选

很多用户不习惯原生GRID的选择模式,所以业务需要用到自定义的选择框来进行数据的操作,显示效果如图所示,增加一条选择列,且配置全选和全选全选的按钮功能,如下图所示。 实现这种功能需要用到Fieldcat的参数控制以及GUI…

18 SpringMVC实战

18 SpringMVC实战 1. 课程介绍2. Spring与Spring MVC环境配置 1. 课程介绍 2. Spring与Spring MVC环境配置

Vue 卸载eslint

卸载依赖 npm uninstall eslint --save 然后 进入package.json中,删除残留信息。 否则在执行卸载后,运行会报错。 之后再起项目。

git之分支管理

一.理解分支 我们看下面这张图片: 在版本回退⾥,你已经知道,每次提交,Git都把它们串成⼀条时间线,这条时间线就可以理解为是⼀个分⽀。截⽌到⽬前,只有⼀条时间线,在Git⾥,这个分⽀…

如何使用Lychee+cpolar搭建本地私人图床并实现远程访问存储图片

文章目录 1.前言2. Lychee网站搭建2.1. Lychee下载和安装2.2 Lychee网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 1.前言 图床作为图片集中存放的服务网站,可以看做是云存储的一部分,既可…

短链接的背后故事:为互联网用户带来的便捷与安全

title: 短链接的背后故事:为互联网用户带来的便捷与安全 date: 2024/2/26 14:58:58 updated: 2024/2/26 14:58:58 tags: 短链接技术起源长URL问题解决链接分享便利性链接跟踪与分析链接管理效率提升链接安全保障应用领域广泛 一、短链接的起源 短链接是一种将长UR…

高效备考2025年AMC8数学竞赛:做熟2000-2024年AMC8真题

这段时间,有小学生的家长朋友问,想给孩子学点数学拓展的知识,不局限于课堂所学。有什么可以推荐的活动或比赛。我个人的建议是可以了解下AMC8美国数学竞赛。主要有以下几个原因: 一是这个竞赛是美国数学协会(MAA&…

STM32程序(移植)中头文件的路径

例:#include "./BSP/LCD/lcd.h"为什么有的头文件加了路径? 先看AI的回答: 在C和C编程中,当我们在源文件中包含(或称为“引入”或“导入”)一个头文件时,编译器需要知道这个头文件的位置。通常,编译器会在…

聚水潭和金蝶云星空接口打通对接实战

聚水潭和金蝶云星空接口打通对接实战 对接系统聚水潭 聚水潭成立于2014年,创始人兼CEO骆海东拥有近三十年传统及电商ERP的研发和实施部署经验。聚水潭创建之初,以电商SaaSERP切入市场,凭借出色的产品和服务,快速获得市场的肯定。随…

C# TesseractOCR识别身份证号

https://github.com/tesseract-ocr/tessdata 新建控制台项目并添加包 Tesseract和Tesseract.Drawing 下载训练的模型 地址 代码实现 using Tesseract;var filePath "F:\\Desktop\\韦小宝.png"; var exePath AppDomain.CurrentDomain.BaseDirectory; var …