生成式AI领域的最新成果都在这里!抢 QCon 展区门票 了解详情
写点什么

实战攻略:利用 GitOps 在 Kubernetes 上实现持续交付

  • 2019-09-12
  • 本文字数:8299 字

    阅读完需:约 27 分钟

实战攻略:利用GitOps在Kubernetes上实现持续交付

GitOps 是 Weaveworks 提出的一种持续交付方式。其工作原理,是利用 Git 作为声明基础设施与应用程序的单一事实来源。


本文我们将通过一个简单的项目,了解如何设置典型的 CI/CD 流水线,而后通过修改将 GitOps 添加到其中。同时,我们还将演示 Flux——GitOps 的核心组件。几周之前,Flux 已经被 CNCF 正式接纳为沙箱培养项目。

我们要做什么

下面,我们先来看看整个流程中的具体操作步骤:


  • 对 GitOps 进行简单介绍

  • 设置一个简单的项目,并在 GitLab 之内进行管理

  • 集成一个 Kubernetes 集群

  • 设置一条典型的 CI/CD 流水线

  • 利用 GItOps 处理其中的 CD 部分

什么是 GitOps?

GitOps 是一种持续交付实现方式。其将 Git 作为声明基础设施与应用程序的事实来源。当对 Git 进行变更时,自动交付流水线也会对您的基础设施进行相应变更。

将变更部署至集群:push 与 pull

在一条典型的 CI/CD 流水线当中,CI 工具负责运行测试、构建镜像、检查 CVE 并将新镜像重新部署至集群当中,具体如下图所示。



典型的 CI/CD 流水线(图片来源:Weaveworks)


GitOps 方法的区别在于,其中的部署部分不再由 CI 工具完成,而是由操作程序通过集群内 Pod 中的运行进程完成(由 Flux 负责实现)。



包含 GitOps 的 CI/CD 流水线 (图片来源:Weaveworks)

相关组件

下图所示为在 Kubernetes 集群当中使用 GitOps 时所需要用到的各组件。



在 Kubernetes 集群当中的各 GitOps 组件 (图片来源:Weaveworks)


为了简单起见,Flux 守护程序会不断运行并检查是否存在新的 Docker 镜像。检测到新镜像之后,它会调用 API Server 对当前正在运行的部署加以更新。


在本文的最后一部分中,我们将设置 Flux 并利用它部署一款简单的应用程序。

我们的项目

在这里,我们使用一个非常简单的 Flask 应用程序。项目的复杂程度并不重要,真正重要的是理解整个 CI/CD 流程的实现方式。

源代码

我们只需要考虑以下文件:


  • app.py 用于公开一个单独的 HTTP 端点并返回一个字符串


from flask import Flaskapp = Flask(__name__)
@app.route("/")def hello(): return "Hello World!"
if __name__ == "__main__": app.run(host='0.0.0.0', port=8000)

复制代码


  • requirements.txt 用于定义 app.py 所需要的依赖性,即 Flask 库


Flask==1.0.2```


  • Dockerfile 用于通过源代码构建起一套镜像


FROM python:3-alpineCOPY . /appWORKDIR /appRUN pip install -r requirements.txtCMD python /app/app.py
复制代码

确保一切正常

我们先通过以下命令为我们的应用程序创建第一套 Docker 镜像:


$ docker image build -t hello:1.0 .
复制代码


在镜像构建完毕之后,我们可以运行容器以使用该镜像。


$ docker container run -p 8000:8000 hello:1.0 * Serving Flask app “app” (lazy loading) * Environment: production
复制代码


注意:不要在生产环境当中使用该开发服务器,请使用生产 WSGI 服务器作为替代。


* Debug mode: off
* Running on http://0.0.0.0:8000/ (Press CTRL+C to quit)
复制代码


我们的服务器在监听端口 8000,如下图所示。


GitLab 项目

我们将利用 GitLab 管理这款应用程序,下面创建一个名为 hello 的新项目:



在 GitLab 当中创建一个新项目


接下来,我们可以为该应用程序文件夹进行 git 初始化,并将一切 push 至 GitLab 项目当中:


$ git init$ git remote add origin git@gitlab.com:lucj/hello.git$ git add .$ git commit -m "Initial commit"$ git push -u origin master
复制代码


几秒之后,我们可以通过 GitLab 的 Web 界面看到该项目中的三个文件。



代码的首次 commit

迎接 Kubernetes

由于需要在 Kubernetes 集群上部署我们的应用程序,所以这里我们将使用 GitLab 的 Kubernetes 集成功能将外部集群的配置导入项目当中。

创建一个托管集群

DOKS (DigitalOcean 托管 Kubernetes 集群) 是我个人最喜爱的解决方案,其易于设置及使用。我们可以通过 DigitalOcean Web 界面或者使用专门的 doctl 命令行界面进行创建。在本示例中,我们将设置一套包含 3 个工作节点的集群,其中管理器节点由 DIgitalOcean 替我们管理。



通过 DigitalOcean 的 Web 界面创建托管 Kubernetes 集群


配置基础设施以及创建集群大概需要几分钟的时间。完成之后,我们需要检索 kubeconfig 文件,以确保我们的 kubectl 客户端能够与集群的 API 服务器通信。我们将使用 doctl 命令并将该配置保存在 k8s-demo.cfg 文件当中:


$ doctl k8s cluster cfg show k8s-demo > k8s-demo.cfg
复制代码


接下来,我们配置 kubectl 以使其与我们的集群进行通信,从而设置 KUBECONFIG 环境变量:


$ export KUBECONFIG=$PWD/k8s-demo.cfg
复制代码


搞定。下面我们来看看目前的集群状态:


$ kubectl get nodesNAME            STATUS   ROLES    AGE     VERSIONk8s-demo-rlf5   Ready    <none>   2m10s   v1.15.2k8s-demo-rlfh   Ready    <none>   2m40s   v1.15.2k8s-demo-rlfk   Ready    <none>   2m33s   v1.15.2
复制代码

与 GitLab 项目相集成

通过 GitLab 的 Web 界面,我们可以轻松将外部 Kubernetes 集群集成至项目当中。我们只需要进入 Operations > Kubernetes,而后点击 Add Kubernetes cluster 即可:



Kubernetes 集群的集成操作


接下来,我们需要选择 Add existing cluster 选项卡。在这里,我们需要填写几个字段,其中第一个字段可以从配置文件当中轻松检索到:



需要在 Kubernetes 集群集成过程中填写的字段


  • 集群名称

  • API Server 的 URL

  • 集群的 CA 证书


要向 GitLab 当中添加集群 CA 证书,我们需要解码配置中指定的证书(以 base64 形式编码)。


$ kubectl config view --raw \-o=jsonpath='{.clusters[0].cluster.certificate-authority-data}' \| base64 --decode
复制代码


  • 服务令牌


整个令牌获取过程分为几个步骤。我们首先需要创建一个 ServiceAccount,并为其提供 cluster-admin 角色。具体操作命令如下:


$ cat <<EOF | kubectl apply -f -apiVersion: v1kind: ServiceAccountmetadata:  name: gitlab-admin  namespace: kube-system---apiVersion: rbac.authorization.k8s.io/v1beta1kind: ClusterRoleBindingmetadata:  name: gitlab-adminroleRef:  apiGroup: rbac.authorization.k8s.io  kind: ClusterRole  name: cluster-adminsubjects:- kind: ServiceAccount  name: gitlab-admin  namespace: kube-systemEOF
复制代码


在 ServiceAccount 创建完成之后,我们开始检索相关 Secret:


$ SECRET=$(kubectl -n kube-system get secret | grep gitlab-admin | awk '{print $1}')add extract its JWT token, the one we need to enter in the Service Token field in the GitLab interface:$ TOKEN=$(kubectl -n kube-system get secret $SECRET -o jsonpath='{.data.token}' | base64 --decode) && echo $TOKEN
复制代码


在对集群集成进行验证之前,我们取消原本被选中的 GitLab-managed-cluster 复选框,这代表着我们将自行管理命名空间。



在集群集成完毕后,GitLab 即可通过 Helm 图表一键安装多款应用程序。不过这不是今天讨论的重点,因此不再赘述。



Kubernetes 集群与我们的 GitLab 项目顺利集成

设置一条典型的 CI/CD 流水线


我们首先在项目的 root 目录处添加一个.gitlab-ci.yml 文件。该文件用于定义每当有新代码被提交至代码库时,所应触发的具体操作。


在文件开头,我们首先定义流水线中的不同阶段:


stages:  - package  - test  - push  - deploy
复制代码


在各个阶段当中,我们进一步定义需要执行的操作:


  • 其中的 package 阶段负责利用源代码创建一套 Docker 镜像,并使用一个临时标签(我们稍后将详加解释)将其推送至项目的 GitLab 镜像库。


build:  image: docker:stable  stage: package  services:    - docker:dind  script:   - docker build -t $CI_REGISTRY_IMAGE:tmp .   - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY   - docker push $CI_REGISTRY_IMAGE:tmp  only:  - master
复制代码


  • 而 test 阶段则负责利用新创建的镜像运行一套容器,并确保返回的消息以“Hello”为开头。


test:  image: docker:stable  stage: test  services:    - docker:dind  script:    - docker run -d --name hello $CI_REGISTRY_IMAGE:tmp    - sleep 10s    - TEST=$(docker run --link hello lucj/curl -s http://hello:8000)    - $([ "${TEST:0:5}" = "Hello" ])  only:  - master
复制代码


接下来的 push 阶段向该镜像中 push 新的标签,第一个标签基于该 git 提交的 hash,第二个为当前分支的名称(在本示例中为 master,因为我们只需要在主分支上进行操作)。最后,将这些新标签 push 回 GitLab 库。


push:  image: docker:stable  stage: push  services:    - docker:dind  script:   - docker image pull $CI_REGISTRY_IMAGE:tmp   - docker image tag $CI_REGISTRY_IMAGE:tmp $CI_REGISTRY_IMAGE:$CI_BUILD_REF   - docker image tag $CI_REGISTRY_IMAGE:tmp $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME   - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY   - docker push $CI_REGISTRY_IMAGE:$CI_BUILD_REF   - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME  only:  - master
复制代码


最后,deploy 的阶段负责在我们的 Kubernetes 集群之内创建/更新应用程序。我们将在 k8s 文件夹当中定义 2 个 manifest 文件:Deployment 用于我们我们 Web 服务器的 Pod,而 Service 则用于将其面向外部公开。


我们首先定义以下 k8s/deploy.tpl 模板。它将被用于在 deploy 阶段生成用于指定 Deployment 资源的 k8s/deploy.yml 文件。这套模板将定义 Deployment,用于管理根据registry.gitlab.com/lucj/hello镜像建立的Pod的一套单独副本。


在这套模板中,我们使用名为 GIT_COMMIT 的占位符替换实际提交的 hash,具体如下所示。


apiVersion: apps/v1kind: Deploymentmetadata:  name: hello  labels:    app: hellospec:  selector:    matchLabels:      app: hello  template:    metadata:      labels:        app: hello    spec:      containers:      - name: hello        image: registry.gitlab.com/lucj/hello:GIT_COMMIT
复制代码


我们还在 k8s/service.yml 当中定义了 Service 资源,用以向外部公开我们的应用程序。Service 的类型为 LoadBalancer。


apiVersion: v1kind: Servicemetadata:  name: hellospec:  type: LoadBalancer  ports:    - name: hello      port: 80      targetPort: 8000      protocol: TCP  selector:    app: hello
复制代码


需要在 deploy 阶段执行的操作如下:


deploy:  stage: deploy  image: lucj/kubectl:1.15.2  environment: test  script:    - kubectl config set-cluster my-cluster --server=${KUBE_URL} --certificate-authority="${KUBE_CA_PEM_FILE}"    - kubectl config set-credentials admin --token=${KUBE_TOKEN}    - kubectl config set-context my-context --cluster=my-cluster --user=admin --namespace default    - kubectl config use-context my-context    - cat k8s/deploy.tpl | sed 's/GIT_COMMIT/'"$CI_BUILD_REF/" > k8s/deploy.yml    - kubectl apply -f k8s  only:  - master
复制代码


另外几点注意事项:


  • 这一阶段需要在包含 kubectl 客户端的镜像上下文中运行

  • 从 GitLab 自动设置的环境变量当中检索集群信息,这些信息将用于设置 Kubernetes 的上下

  • Deployment 资源根据模板文件创建而成,其中的 CIT_COMMIT 占位符将被替换为 $CI_BUILD_REF 环境变量当中的实际提交信息

  • Service 与 Deployment 资源分别位于 k8s/service.yml 与 k8s/deploy.yml 当中,通过常用的“kubectl apply”命令进行创建/更新


备注:这条流水线非常简单,但并不是最优方案。我们只是利用它来展示不同的流程。

项目测试

下面,让我们把这些变更 push 到 GitLab 项目当中,而后检查由此触发的 CI/CD 流水线:


$ git add k8s$ git commit -m ‘Add K8s resources’$ git add .gitlab-ci.yml$ git commit -m ‘Add GitLab pipeline’$ git push origin master
复制代码


这会触发 GitLab 流水线,具体如下图中的 Web 界面所示:



在该流水线的 deploy 阶段(最终阶段),Deployment 与 Service 需要进行首次创建(因为之前并不存在)。由于 Service 的类型为 LoadBalancer,因此我们可以从下图中看到 DigitalOcean 基础设施上会创建对应的负载均衡器资源。



利用与该 Load Balancer 相关联的外部 IP 地址,我们可以在指向运行有应用程序的底层 Pod 的端口 80 上访问自己的应用程序。



这证明 Service 与 Deployment 都已经正确创建完成。


下面,我们需要对 app.py 做出一点调整,把返回内容由“Hello World!”改为“Hello from Kube”。


from flask import Flaskapp = Flask(__name__)@app.route("/")def hello():    return "Hello from Kube"if __name__ == "__main__":    app.run(host='0.0.0.0', port=8000)
复制代码


我们对这些变更进行 commit 与 push:


$ git add app.py$ git commit -m 'change message to Hello from Kube'$ git push origin master
复制代码


新的 CI/CD 流水线由此触发,我们可以刷新浏览器并看到如下结果:



当然,我们在这里设置的只是一条简单的流水线。对于真实场景中的应用程序,还需要添加一些额外的增强功能。例如,我们可能需要考虑以下步骤:


  • 额外的测试

  • 镜像扫描,用于确保该镜像不包含任何 CVE 漏洞(或者至少不存在高危漏洞)


若需了解更多与镜像扫描相关的细节信息,请参阅:


https://medium.com/better-programming/adding-cve-scanning-to-a-ci-cd-pipeline-d0f5695a555a

添加 GitOps

现在,我们需要再次修改这条 CI/CD 流水线,以利用 GItOps 方法处理其中的 CD 部分。以下结构展示了 GitOps Deployment 工作流中所涉及的组件。



GitOps Deployment 工作流(图片来源:Weaveworks)


基本上,每当系统在镜像注册表中检测到新的镜像标签,我们就要利用 Flux 操作程序(运行在 Pod 内的集群当中)对应用程序进行重新部署。

安装 Flux

Flux 可以通过 Deployment 或者 Helm 进行手动安装。在本文中,我们也使用手动方案。第一步,就是对 fluxcd 库进行 clone:


$ git clone https://github.com/fluxcd/flux && cd flux
复制代码


接下来,在 Deployment 规范之内(deploy/flux-deployment.yaml)变更以下参数:


  • --git-url=git@gitlab.com:lucj/hello, 用于告知 Flux 检测哪个 Git 库

  • –git-path=k8s, 在此库当中只考虑 k8s 文件夹(我们的 Kubernetes manifests 文件就位于该文件夹内)

  • –git-ci-skip, 此选项允许我们在 Flux 完成对 GitLab 项目库的更新之后(包括标签与 Deployment 资源更新),跳过 CI 流水线


现在,我们可以将 Flux 部署至集群当中了:


$ kubectl apply -f deployserviceaccount/flux createdclusterrole.rbac.authorization.k8s.io/flux createdclusterrolebinding.rbac.authorization.k8s.io/flux createddeployment.apps/flux createdsecret/flux-git-deploy createddeployment.apps/memcached createdservice/memcached created
复制代码


由此创建的几种资源:


  • ServiceAccount、ClusterRole 以及 ClusterRoleBoinding,用于为 Flux Pod 提供运行所需的验证/授权

  • Flux 操作程序

  • 用于 memcached 的 Service 与 Deployment,由 Flux 用于缓存镜像元数据


$ kubectl get podsNAMESPACE NAME                       READY  STATUS   RESTARTS  AGEdefault   flux-dcb965db7-pn97k       1/1    Running  0         56sdefault   memcached-554f994578-t2tss 1/1    Running  0         56s...
复制代码


查看 Flux Pod 日志,我们会看到一条错误消息,因为 Flux 无法读取项目的 Git 库。


“权限被拒绝(公钥)。严重:无法从远程库中读取。请确保您具有正确的访问权限,且目标库存在。”


为了解决这个问题,我们可以使用 fluxctl 实用程序检索安装期间所公开 ssh 密钥。


$ fluxctl identityssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCx4fk4YjcM7cP1FL/AKWtHpN+cg9/Qz1p5dzAlsFLMKilUUy0uCQQmaptXDZQGaZrbvNSyezgT5/yH6qau6W6ICoLYAzBku47PoWlqbUfcbPhMxHSfivjv7s4lSeUE+u3kR2opROxdyHHL+VQMI6n9Xc7qnTq6YC+VJ+RkoUUd0bgBC+Rg/aMURLD9mkAVzmWw6+Y8QAJMVNMzNDgId+8iSHKtOYsHqoxg4GqexdB1R5goE0ChBU9DPsiqLfk8jzuD2I3xuZeGW6or+/JHxa/6vO8lX+of1ZGZGZKr5i3E4OIehSwFUP2A/ypeqXEEI5gmO1s2YrM49jpS+jW4oUMP
复制代码


接下来,将该密钥添加至我们的 GitLab 库,为其提供创建/更新所需要的读取/写入访问权限。


修改原有流水线

由于 Flux 负责对项目作出的变更进行部署,因此我们需要删除之前创建的.gitlab-ci.yml 文件中的 Deployment 阶段,其它内容则保持不变。现在的.gitlab-ci.yml 如下所示,其中与集群 API Server 交互的 kubectl 已经被删除:


stages:  - package  - test  - pushbuild:  image: docker:stable  stage: package  services:    - docker:dind  script:   - docker build -t $CI_REGISTRY_IMAGE:tmp .   - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY   - docker push $CI_REGISTRY_IMAGE:tmp  only:  - mastertest:  image: docker:stable  stage: test  services:    - docker:dind  script:    - docker run -d --name hello $CI_REGISTRY_IMAGE:tmp    - sleep 10s    - TEST=$(docker run --link hello lucj/curl -s http://hello:8000)    - $([ "${TEST:0:5}" = "Hello" ])  only:  - masterpush:  image: docker:stable  stage: push  services:    - docker:dind  script:   - docker image pull $CI_REGISTRY_IMAGE:tmp   - docker image tag $CI_REGISTRY_IMAGE:tmp $CI_REGISTRY_IMAGE:$CI_BUILD_REF   - docker image tag $CI_REGISTRY_IMAGE:tmp $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME   - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY   - docker push $CI_REGISTRY_IMAGE:$CI_BUILD_REF   - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME  only:  - master
复制代码


此外,我们也可以删除 k8s/deploy.tpl 模板文件,因为我们不再需要利用该文件对 Deployment manifest 进行更新。相反,我们将在 Deployment 中使用以下 k8s/deploy.yml,确保 Flux 在每次检测到新的镜像标签时都会执行更新。


apiVersion: apps/v1kind: Deploymentmetadata:  name: hello  annotations:    flux.weave.works/automated: "true"    flux.weave.works/tag.hello: regexp:^((?!tmp).)*$  labels:    app: hellospec:  selector:    matchLabels:      app: hello  template:    metadata:      labels:        app: hello    spec:      containers:      - name: hello        image: registry.gitlab.com/lucj/hello:master
复制代码


用于该 Deployment 的 Flux 配置在 annotations 键内完成:


  • flux.weave.works/automated: “true”, 用于激活该资源的自动重新部署

  • flux.weave.works/tag.hello: regexp:^((?!tmp).)*$, 确保具有 tmp 标签的临时镜像不会被纳入使用

实际测试

我们对 app.py 中的代码进行了如下变更,因此其现在会返回“Hello from Flux”。


from flask import Flaskapp = Flask(__name__)@app.route("/")def hello():    return "Hello from Flux"if __name__ == "__main__":    app.run(host='0.0.0.0', port=8000)
复制代码


接下来将修改后的内容 push 至 GitLab。


$ git rm k8s/deploy.tpl$ git add k8s/deploy.yml .gitlab-ci.yml app.py$ git commit -m 'CD with Flux'$ git push origin master
复制代码


查看 GitLab 界面,我们会看到该流水线已经被触发了多次。



已经创建的多条流水线(其中几条被直接跳过)


有条流水线的触发原因是我们做出了变更,其它几条则由 Flux 在对 master 分支上的 Deployment manifest(k8s/deploy.yml)以及 flux-sync 分支上的标签进行更新时触发。除了这两项操作之外的其它被触发流水线被直接跳过(相关操作并未执行),这是因为我们在 Flux 配置当中使用了—git-ci-skip 选项(如果不这样,流水线将一直循环运行)。


然后,我们可以再次刷新浏览器以查看应用程序的最新版本。



可以看到,当 Flux 操作程序定期检查新的镜像标签时,其会发现 CI 流水线执行期间出现的代码变更,并据此自动更新 Deployment。

总结

在本文当中,我希望向大家介绍 GitOps,并通过一个简单的示例说明它如何与 GitLab CI 流水线配合起效。大家也可以根据需求增强其中某些功能,例如在流水线当中定义更多阶段,使用 sermver 命名镜像标签等……总之,我希望这篇简单的文章能够让大家对整个方法拥有基本的了解。


GitOps 在很长一段时间内得到了行业的高度关注,感兴趣的朋友可以点击此处通过官方文档了解更多细节信息。


您已经开始使用 GitOps 方案了吗?希望在评论中看到您的分享心得。


2019-09-12 16:584832

评论

发布
暂无评论
发现更多内容

INFINI Gateway 与华为鲲鹏完成产品兼容互认证

极限实验室

Gateway 极限科技 华为鲲鹏认证

Clop Pro for mac(图片优化工具)v2.4.0激活版下载

影影绰绰一往直前

云游戏迎来新时代,华为云轻应用服务器引领数字化创新

YG科技

华为云三重优惠!云耀L实例轻松应对挑战,助您企业更高效上云

YG科技

NGINX 和 NGINX PLUS 缓存指南

NGINX开源社区

nginx 性能优化 NGINX PLUS 缓存调优 分割缓存

office办公套件:Office LTSC 2021 for Mac v16.81 beta版

加油,小妞!

office办公套件 办公套件 Microsoft office

高性能云计算,华为云服务器行业遥遥领先

平平无奇爱好科技

HarmonyOS后台任务管理开发指南上线!

HarmonyOS开发者

HarmonyOS

SmartSVN for Mac(SVN客户端)v14.4激活版

影影绰绰一往直前

华为云耀云服务器L实例多重防护助力企业放心上云

YG科技

Dropzone 4 for mac(文件拖拽增强工具)v4.80.0激活版

影影绰绰一往直前

Keka for Mac(压缩解压工具) 1.3.6中文版

展初云

Mac 解压缩软件 Keka

华为云耀云服务器L实例:在小程序竞争中的强大利器

YG科技

Programming Abstractions in C阅读笔记:p202-p234

codists

A16Z领头的Story Protocol有什么看点?

币离海

BRC20 Story Protocol

苹果电脑压缩解压工具:Keka for Mac中文版

加油,小妞!

Keka Mac 压缩解压工具

兴湘集团司库管理平台成功上线,打造国企数智管理新标杆!

用友BIP

企业数智化

Dropzone 4 for Mac(文件拖拽操作增强工具)

展初云

效率工具 Mac Dropzone 4

OpenHarmony Meetup 2023北京站圆满举办

OpenHarmony开发者

OpenHarmony

华为云耀云服务器L实例,助力初创及成长型企业实现高效数字化转型

平平无奇爱好科技

稳定高效选择,华为云耀云服务器L实例助力云端创新

平平无奇爱好科技

华为云耀云服务器L实例,助力企业开启轻松云计算之旅

YG科技

8. 业务中台架构

Joy

CI/CD 构建中能保护好 SSHKEY吗?

极狐GitLab

DevOps SSH CI/CD SSH Key

Mitti for Mac(视频回放编辑工具)v2.5.6激活版

影影绰绰一往直前

多功能视频播放器Infuse中文激活版最新

mac大玩家j

Mac软件 视频播放器 音视频工具

简单上云第一步的华为云服务器,助力中小企业提升业务

平平无奇爱好科技

华为云耀云服务器L实例:创新的解决方案,推动小程序与网站开发新趋势

YG科技

比特币首次减半11 周年:从 12 美元涨至 37,000 美元

币离海

BTC 减半

Kubernetes 漫游:kube-scheduler

Phoenix

云原生 kubernetes 运维

Mac电脑PDF 批量处理软件:BatchOutput PDF激活版

胖墩儿不胖y

Mac软件 pdf处理工具 好用的PDF编辑器

实战攻略:利用GitOps在Kubernetes上实现持续交付_开源_Luc Juggery_InfoQ精选文章