写点什么

使用 Kubernetes 来管理 Docker 扩展

  • 2015-10-26
  • 本文字数:10274 字

    阅读完需:约 34 分钟

Kubernetes 是一款开源的项目,管理 Linux 容器集群,并可将集群作为一个单一的系统来对待。其可跨多主机来管理和运行 Docker 容器、提供容器的定位、服务发现以及复制控制。它由 Google 发起,现在得到了如微软、红帽、IBM 和Docker 等众多厂商的支持。

Google 使用容器技术有着超过十年的历史,每周要启动超过2 亿台容器。通过Kubernetes,Google 分享了他们关于容器的专业经验,即创建大规模运行容器的开放平台。

一旦用户开始使用Docker 容器,那么问题就来了,一、如何跨多个Docker 主机扩展和启动容器,且能够在主机间平衡这些容器。二、还需要高度抽象出API 来定义如何从逻辑上组织容器、定义容器池、负载均衡以及关联性。该项目就是为了解决这两个问题而生的。

Kubernetes 仍然处于早期阶段,这也就意味着会有很多新的变化进入到该项目,目前还仅有比较简单的实例,仍需要加入新的功能,但是它正在一步一步地平稳发展,也得到了很多大公司的支持,前途无量。

Kubernetes 概念

Kubernetes 的架构被定义为由一个 master 服务器和多个 minons 服务器组成。命令行工具连接到 master 服务器的 API 端点,其可以管理和编排所有的 minons 服务器,Docker 容器接收来自 master 服务器的指令并运行容器。

  • Master:Kubernetes API 服务所在,多 Master 的配置仍在开发中。
  • Minons:每个具有 Kubelet 服务的 Docker 主机,Kubelet 服务用于接收来自 Master 的指令,且管理运行容器的主机。
  • Pod :定义了一组绑在一起的容器,可以部署在同一 Minons 中,例如一个数据库或者是 web 服务器。
  • Replication controller :定义了需要运行多少个 Pod 或者容器。跨多个 minons 来调度容器。
  • Service:定义了由容器所发布的可被发现的服务/端口,以及外部代理通信。服务会映射端口到外部可访问的端口,而所映射的端口是跨多个 minons 的 Pod 内运行的容器的端口。
  • kubecfg:命令行客户端,连接到 master 来管理 Kubernetes。

(点击图片可放大显示)

Kubernetes 由状态所定义,而不是进程。当你定义了一个 pod 时,Kubernetes 会设法确保它会一直运行。如果其中的某个容器挂掉了,Kubernetes 会设法再启动一个新的容器。如果一个复制控制器定义了 3 份复制,Kubernetes 会设法一直运行这 3 份,根据需要来启动和停止容器。

本文中所用的例子应用是 Jenkins 持续集成服务,Jenkins 是典型的通过主从服务来建立分布式的工作任务的例子。Jenkins 由 jenkins swarm 插件来配置,运行一个 jenkins 主服务和多个 jenkins 从服务,所有的 Jenkins 服务都以 Docker 容器的方式跨多个主机运行。swarm 从服务在启动时连接到 Jenkins 主服务,然后就可以运行 Jenkins 任务了。例子中使用的配置文件可以从Github 上下载到,而该Docker 镜像可以从 casnchez/jenkins-swarm 获取,对于 Jenkins 主服务来说,可以通过 swarm 插件来扩展官方 Jenkins 镜像。对于 jenkins 从服务来说,可以从 csanchez/jenkins-swarm-slave 获取,它只是在 JVM 容器中运行了 jenkins 从服务而已。

创建 Kubernetes 集群

Kubernetes 提供了多个操作系统和云/虚拟化提供商下创建集群的脚本,有 Vagrant(用于本地测试)、Google Compute Engine、Azure、Rackspace 等。

本文所实践的例子就是运行在 Vagrant 之上的本地集群,使用 Fedora 作为操作系统,遵照的是官方入门指南,测试的 Kubernetes 版本是 0.5.4。取代了默认的 3 个 minion(Docker 主机),而是使用了 2 个 minion,两台主机就足以展示 Kubernetes 的能力了,三台有点浪费。

当你下载了 Kubernetes ,然后将至解压后,即可在本地的目录下运行这些示例了。初学者创建集群仅需要一个命令,即./cluster/kube-up.sh。

复制代码
$ export KUBERNETES_PROVIDER=vagrant
$ export KUBERNETES_NUM_MINIONS=2
$ ./cluster/kube-up.sh

获取示例配置文件:

复制代码
$ git clone https://github.com/carlossg/kubernetes-jenkins.git

集群创建的快慢取决于机器的性能和内部的带宽。但是其最终完成不能有任何的错误,而且它仅需要运行一次。

命令行工具

和 Kubernetes 交互的命令行工具叫做 kubecfg,此脚本位于 cluster/kubecfg.sh。

为了检验我们刚才创建的两个 minons 已经启动并运行了,只需运行 kubecfg list minions 命令即可,它的结果会显示 Vagrant 的两台虚拟机。

复制代码
$ ./cluster/kubecfg.sh list minions
Minion identifier
----------
10.245.2.2
10.245.2.3

Pod

在 Kubernetes 的术语中,Jenkins 主服务被定义为一个 pod 。在一个 pod 中可以指定多个容器,这些容器均部署在同一 Docker 主机中,在一个 pod 中的容器的优势在于可以共享资源,例如存储,而且使用相同的网络命名空间和IP 地址。默认的卷是空的目录,类型为emptyDir,它的生存时间就是pod 的生命周期,而并非是指定的容器,所以如果一个容器失效了,但是其持久性的存储仍然在。另外一个卷的类型是hostDir,它是将主机的一个目录挂载到容器中。

在这个具体的Jenkins 示例中,我们所创建的pod 是两个容器,分别是Jenkins 主服务和MySQL,前者作为实例,后者作为数据库。然而我们只需要关心jenkins 主服务容器即可。

为了创建一个Jenkins pod,我们运行定义了Jenkins 容器pod 的kubecfg,使用Docker 镜像csanchez/jenkins-swarm,为了能够访问Jenkins 的Web 界面和从服务的API,我们将主机的端口8080 和50000 映射到容器,以及将/var/jenkins_home 挂载为卷。读者可从 Github 下载到示例代码。

Jenkins Web 界面的 pod(pod.json)的定义如下:

复制代码
{
"id": "jenkins",
"kind": "Pod",
"apiVersion": "v1beta1",
"desiredState": {
"manifest": {
"version": "v1beta1",
"id": "jenkins",
"containers": [
{
"name": "jenkins",
"image": "csanchez/jenkins-swarm:1.565.3.3",
"ports": [
{
"containerPort": 8080,
"hostPort": 8080
},
{
"containerPort": 50000,
"hostPort": 50000
}
],
"volumeMounts": [
{
"name": "jenkins-data",
"mountPath": "/var/jenkins_home"
}
]
}
],
"volumes": [
{
"name": "jenkins-data",
"source": {
"emptyDir": {}
}
}
]
}
},
"labels": {
"name": "jenkins"
}
}

然后使用下面命令来创建:

复制代码
$ ./cluster/kubecfg.sh -c kubernetes-jenkins/pod.json create pods
Name Image(s) Host Labels Status
---------- ---------- ---------- ---------- ----------
jenkins csanchez/jenkins-swarm:1.565.3.3 <unassigned> name=jenkins Pending</unassigned>

这需要等待一段时间,具体时间的长短要视你的网络而定,因为它会从 Docker Hub 上下载 Docker 镜像到 minion,我们可以查看它的状态,以及在那个 minion 中启动了。

复制代码
$ ./cluster/kubecfg.sh list pods
Name Image(s) Host Labels Status
---------- ---------- ---------- ---------- ----------
jenkins csanchez/jenkins-swarm:1.565.3.3 10.0.29.247/10.0.29.247 name=jenkins Running

如果我们使用 SSH 登录到 minion 中,此 minion 即是 pod 被分配到的 minion-1 或 minion-2,我们就可以看到 Docker 按照所定义的那样启动起来了。其中还包括了用于 Kubernetes 内部管理的容器(kubernetes/pause 和 google/cadvisor)。

复制代码
$ vagrant ssh minion-2 -c "docker ps"
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7f6825a80c8a google/cadvisor:0.6.2 "/usr/bin/cadvisor" 3 minutes ago Up 3 minutes k8s_cadvisor.b0dae998_cadvisormanifes12uqn2ohido76855gdecd9roadm7l0.default.file_cadvisormanifes12uqn2ohido76855gdecd9roadm7l0_28df406a
5c02249c0b3c csanchez/jenkins-swarm:1.565.3.3 "/usr/local/bin/jenk 3 minutes ago Up 3 minutes k8s_jenkins.f87be3b0_jenkins.default.etcd_901e8027-759b-11e4-bfd0-0800279696e1_bf8db75a
ce51fda15f55 kubernetes/pause:go "/pause" 10 minutes ago Up 10 minutes k8s_net.dbcb7509_0d38f5b2-759c-11e4-bfd0-0800279696e1.default.etcd_0d38fa52-759c-11e4-bfd0-0800279696e1_e4e3a40f
e6f00165d7d3 kubernetes/pause:go "/pause" 13 minutes ago Up 13 minutes 0.0.0.0:8080->8080/tcp, 0.0.0.0:50000->50000/tcp k8s_net.9eb4a781_jenkins.default.etcd_901e8027-759b-11e4-bfd0-0800279696e1_7bd4d24e
7129fa5dccab kubernetes/pause:go "/pause" 13 minutes ago Up 13 minutes 0.0.0.0:4194->8080/tcp k8s_net.a0f18f6e_cadvisormanifes12uqn2ohido76855gdecd9roadm7l0.default.file_cadvisormanifes12uqn2ohido76855gdecd9roadm7l0_659a7a52

还有,我们一旦拿到了容器的 ID,就可以通过如 vagrant ssh minion-1 -c "docker logs cec3eab3f4d3"这样的命令来查看容器的日志了。

我们也可以访问 Jenkins 的 Web 界面,至于要访问的 URL 是 http://10.245.2.2:8080/ 还是 http://10.0.29.247:8080/,要看用到了那个 minion。

服务发现

Kubernetes 支持定义服务,一种为容器使用发现和代理请求到合适的 pod 的方法。下面示例使用了 service-http.json 文件来创建一个服务,其将 id 为 jenkins 指向了贴有标签为 name=jenkins 的 pod。而标签是在如上面 pod 的定义中所声明的。且将端口 8888 重定向到容器的 8080 。

复制代码
{
"id": "jenkins",
"kind": "Service",
"apiVersion": "v1beta1",
"port": 8888,
"containerPort": 8080,
"selector": {
"name": "jenkins"
}
}

使用 kubecfg 来创建服务:

复制代码
$ ./cluster/kubecfg.sh -c kubernetes-jenkins/service-http.json create services
Name Labels Selector IP Port
---------- ---------- ---------- ---------- ----------
jenkins name=jenkins 10.0.29.247 8888

每个服务都会被分配一个唯一的 IP 地址,且此 IP 地址会伴随服务的整个生命周期。如果我们有多个 pod 匹配服务的定义,服务就会在所有它们之上提供负载均衡。

服务的另外一个特性是可以为由 Kubernetes 来运行的容器设置一些环境变量,使其可以连接到该服务容器,这和运行已链接的 Docker 容器有些类似,它可以帮助我们从若干从服务中找到 Jenkins 主服务。

复制代码
JENKINS_PORT='tcp://10.0.29.247:8888'
JENKINS_PORT_8080_TCP='tcp://10.0.29.247:8888'
JENKINS_PORT_8080_TCP_ADDR='10.0.29.247'
JENKINS_PORT_8080_TCP_PORT='8888'
JENKINS_PORT_8080_TCP_PROTO='tcp'
JENKINS_SERVICE_PORT='8888'
SERVICE_HOST='10.0.29.247'

在此示例中,我们还需要打开端口 50000,Jenkins swarm 插件需要用这个端口。我们创建另外一个 service-slave.json 文件,这样 Kubernetes 就可以重定向端口到 Jenkins 服务容器了。

复制代码
{
"id": "jenkins-slave",
"kind": "Service",
"apiVersion": "v1beta1",
"port": 50000,
"containerPort": 50000,
"selector": {
"name": "jenkins"
}
}

再次使用 kubecfg 来创建:

复制代码
$ ./cluster/kubecfg.sh -c kubernetes-jenkins/service-slave.json create services
Name Labels Selector IP Port
---------- ---------- ---------- ---------- ----------
jenkins-slave name=jenkins 10.0.86.28 50000

列出所有可用的已经定义的服务,包括一些 Kubernetes 内部的服务:

复制代码
$ ./cluster/kubecfg.sh list services
Name Labels Selector IP Port
---------- ---------- ---------- ---------- ----------
kubernetes-ro component=apiserver,provider=kubernetes 10.0.22.155 80
kubernetes component=apiserver,provider=kubernetes 10.0.72.49 443
jenkins name=jenkins 10.0.29.247 8888
jenkins-slave name=jenkins 10.0.86.28 50000

复制控制器

复制控制器允许在多个 minion 中运行多个 pod。Jenkins 的从服务以此方式来运行,可以确保一直有一个运行 Jenkins 任务的从服务池。

创建 replication.json,所定义的内容如下:

复制代码
{
"id": "jenkins-slave",
"apiVersion": "v1beta1",
"kind": "ReplicationController",
"desiredState": {
"replicas": 1,
"replicaSelector": {
"name": "jenkins-slave"
},
"podTemplate": {
"desiredState": {
"manifest": {
"version": "v1beta1",
"id": "jenkins-slave",
"containers": [
{
"name": "jenkins-slave",
"image": "csanchez/jenkins-swarm-slave:1.21",
"command": [
"sh", "-c", "/usr/local/bin/jenkins-slave.sh
-master http://$JENKINS_SERVICE_HOST:$JENKINS_SERVICE_PORT -tunnel
$JENKINS_SLAVE_SERVICE_HOST:$JENKINS_SLAVE_SERVICE_PORT -username
jenkins -password jenkins -executors 1"
]
}
]
}
},
"labels": {
"name": "jenkins-slave"
}
}
},
"labels": {
"name": "jenkins-slave"
}
}

podTemplate 一节允许和 pod 的定义进行一样的配置。在此示例中,我们打算让 Jenkins 从服务自动地连接到 Jenkins 主服务,而不是利用 Jenkins 主服务的多播发现。要实现此想法,我们需要执行 jenkins-slave.sh 命令,指定参数 -master,从而实现在 Kubernetes 中让从服务连接到主服务。注意,上述配置文件中,我们使用了 Kubernetes 所提供的为 Jenkins 服务定义的环境变量(JENKINS_SERVICE_HOST 和 JENKINS_SERVICE_PORT)。此方法中镜像的命令覆盖了容器的配置,为的是利用已经下载好的镜像。这也同样可以在 pod 的定义中去实现。

使用 kubecfg 来创建副本:

复制代码
$ ./cluster/kubecfg.sh -c kubernetes-jenkins/replication.json create replicationControllers
Name Image(s) Selector Replicas
---------- ---------- ---------- ----------
jenkins-slave csanchez/jenkins-swarm-slave:1.21 name=jenkins-slave 1

现在执行 pod 列表,可以看到新的 pod 已经创建,其数量是由我们刚刚所定义的复制控制器所决定的。

复制代码
$ ./cluster/kubecfg.sh list pods
Name Image(s) Host Labels Status
---------- ---------- ---------- ---------- ----------
jenkins csanchez/jenkins-swarm:1.565.3.3 10.245.2.3/10.245.2.3 name=jenkins Running
07651754-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.2/10.245.2.2 name=jenkins-slave Pending

第一次运行的 jenkins-swarm-slave 镜像是 minion 从 Docker 仓库下载下来的,但是一段时间之后(这取决于你的互联网连接),Jenkins 从服务会自动连接到 Jenkins 主服务。登录到 Jenkins 从服务所在的服务器,docker ps 会显示出运行中的容器和 Docker 的日志,这能够帮助你来调试容器启动中出现的问题。

复制代码
$ vagrant ssh minion-1 -c "docker ps"
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
870665d50f68 csanchez/jenkins-swarm-slave:1.21 "/usr/local/bin/jenk About a minute ago Up About a minute k8s_jenkins-slave.74f1dda1_07651754-4f88-11e4-b01e-0800279696e1.default.etcd_11cac207-759f-11e4-bfd0-0800279696e1_9495d10e
cc44aa8743f0 kubernetes/pause:go "/pause" About a minute ago Up About a minute k8s_net.dbcb7509_07651754-4f88-11e4-b01e-0800279696e1.default.etcd_11cac207-759f-11e4-bfd0-0800279696e1_4bf086ee
edff0e535a84 google/cadvisor:0.6.2 "/usr/bin/cadvisor" 27 minutes ago Up 27 minutes k8s_cadvisor.b0dae998_cadvisormanifes12uqn2ohido76855gdecd9roadm7l0.default.file_cadvisormanifes12uqn2ohido76855gdecd9roadm7l0_588941b0
b7e23a7b68d0 kubernetes/pause:go "/pause" 27 minutes ago Up 27 minutes 0.0.0.0:4194->8080/tcp k8s_net.a0f18f6e_cadvisormanifes12uqn2ohido76855gdecd9roadm7l0.default.file_cadvisormanifes12uqn2ohido76855gdecd9roadm7l0_57a2b4de

复制控制器可以自动的调整任何想要的副本数量:

复制代码
$ ./cluster/kubecfg.sh resize jenkins-slave 2

再次列出 pod 的话,可以看到每个副本是在哪里运行的。

复制代码
$ ./cluster/kubecfg.sh list pods
Name Image(s) Host Labels Status
---------- ---------- ---------- ---------- ----------
07651754-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.2/10.245.2.2 name=jenkins-slave Running
a22e0d59-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.3/10.245.2.3 name=jenkins-slave Pending
jenkins csanchez/jenkins-swarm:1.565.3.3 10.245.2.3/10.245.2.3 name=jenkins Running

调度

目前默认的调度是随机的,但是基于资源的调度很快就将实现了。在写作本文的时候,基于内存和 CPU 利用率的调度还有一些没有修复的问题。还有整合 Apache Mesos 的调度也在紧锣密鼓的进行中。Apache Mesos 是一款分布式系统的框架,提供了资源管理的 API,和跨整个数据中心或云环境的调度。

自愈

使用 Kubernetes 的一大好处就是其能够自动管理和修复容器。

如果运行 Jenkins 服务的容器由于某些原因宕机了,比如说进程崩溃了,那么 Kubernetes 就会得到通知,并会在几秒钟之后创建一个新的容器。

复制代码
$ vagrant ssh minion-2 -c 'docker kill `docker ps | grep csanchez/jenkins-swarm: | sed -e "s/ .*//"`'
51ba3687f4ee
$ ./cluster/kubecfg.sh list pods
Name Image(s) Host Labels Status
---------- ---------- ---------- ---------- ----------
jenkins csanchez/jenkins-swarm:1.565.3.3 10.245.2.3/10.245.2.3 name=jenkins Failed
07651754-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.2/10.245.2.2 name=jenkins-slave Running
a22e0d59-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.3/10.245.2.3 name=jenkins-slave Running

稍后再执行 list pod 命令,一般不会超过 1 分钟,会看到如下内容:

复制代码
Name Image(s) Host Labels Status
---------- ---------- ---------- ---------- ----------
jenkins csanchez/jenkins-swarm:1.565.3.3 10.245.2.3/10.245.2.3 name=jenkins Running
07651754-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.2/10.245.2.2 name=jenkins-slave Running
a22e0d59-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.3/10.245.2.3 name=jenkins-slave Running

将 Jeknkis 的数据目录以卷的形式运行,我们保证了在容器宕机之后仍然能够保留数据,所以不会丢失任何 Jenkins 的任务或者是已经创建好的数据。而且因为 Kubernetes 在每个 minion 都代理了服务,Jenkins 从服务会自动连接到 Jenkins 主服务,而根本无须关心它是在哪里运行的!而且 Jenkins 从服务容器宕掉,系统也会自动创建新的容器,服务发现会将之自动加入到 Jenkins 服务池中。

如果发生了更加严重的情况,比如 minion 挂掉了,Kubernetes 目前还没能提供将现有容器重新调度到其它仍在运行的 minion 中的能力,它仅仅会显示某个 pod 失效,如下所示:

复制代码
$ vagrant halt minion-2
==> minion-2: Attempting graceful shutdown of VM...
$ ./cluster/kubecfg.sh list pods
Name Image(s) Host Labels Status
---------- ---------- ---------- ---------- ----------
jenkins csanchez/jenkins-swarm:1.565.3.3 10.245.2.3/10.245.2.3 name=jenkins Failed
07651754-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.2/10.245.2.2 name=jenkins-slave Running
a22e0d59-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.3/10.245.2.3 name=jenkins-slave Failed

清理

kubecfg 还提供了一些命令来停止和删除复制控制器、pod、以及服务等。

要停止复制控制器,设置复制数为 0,所有 Jenkins 从服务的容器都会被终止:

复制代码
$ ./cluster/kubecfg.sh stop jenkins-slave

删除它:

复制代码
$ ./cluster/kubecfg.sh rm jenkins-slave

删除 Jenkins 服务 Pod,Jenkins 主服务的容器会被终止:

复制代码
$ ./cluster/kubecfg.sh delete pods/jenkins

删除服务:

复制代码
$ ./cluster/kubecfg.sh delete services/jenkins
$ ./cluster/kubecfg.sh delete services/jenkins-slave

总结

Kubernetes 还是一个尚处于早期的项目,但是极有希望来管理 Docker 的跨服务器部署以及简化执行长时间运行和分布式的 Docker 容器。通过抽象基础设施概念,定义状态而不是进程,它提供了集群的简单定义,包括脱离管理的自我修复能力。简而言之,Kubernetes 让 Docker 的管理更加的轻松。

关于作者

Carlos Sanchez在自动化、软件开发质量、QA 以及运维等方面有超过 10 年的经验,范围涉及从构建工具、持续集成到部署自动化,DevOps 最佳实践,再到持续交付。他曾经为财富 500 强的公司实施过解决方案,在多家美国的创业公司工作过,最近任职的公司叫做 MaestroDev,这也是他一手创建的公司。Carlos 曾经在世界各地的各种技术会议上作演讲,包括 JavaOne、EclipseCON、ApacheCON、JavaZone、Fosdem 和 PuppetConf。他非常积极的参与开源,是 Apache 软件基金会的成员,同时也是其它一些开源团体的成员,为很多个开源项目做过贡献,比如 Apache Maven、Fog 和 Puppet。

查看英文原文: Scaling Docker with Kubernetes


感谢夏雪对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群)。

2015-10-26 14:3212227
用户头像

发布了 33 篇内容, 共 12.3 次阅读, 收获喜欢 13 次。

关注

评论

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

Kafka 如何基于 KRaft 实现集群最终一致性协调

AutoMQ

kafka java

碳课堂|一文梳理国际碳标准发展历程

AMT企源

数字化转型 双碳 碳管理 碳核算 碳排放

inBuilder 低代码平台新特性推荐 - 第二十期

inBuilder低代码平台

博睿数据应邀出席双态IT用户大会,分享《构建云原生时代的一体化智能可观测性》

博睿数据

学习音乐必备软件iReal Pro for Mac(优秀的音乐练习参考工具)

Mac相关知识分享

Mac Mac软件 音乐学习软件

您的 API 定价模型有多重要?

幂简集成

API API定价

RAW Power for Mac( RAW 图像文件照片编辑程序) v3.4.22中文激活版

Mac相关知识分享

Mac 图像处理 Mac软件 照片编辑

APU Software APU Dynamics Optimizer for mac(APU 动态优化器)

Mac相关知识分享

Mac软件 mac软件下载 音频插件 音频软件

突破瓶颈,数字化建设的企业经营妙方

优秀

数字化转型 企业经营管理 数字化建设

289M→259M得物包体积治理实践

得物技术

ruby bash ios swift 企业号2024年6月PK榜

【粽子大师】甜咸粽之争来看大师pick谁

AppBuilder

深耕低代码,技术赋能企业转型业务

不在线第一只蜗牛

低代码 企业转型

原来Stable Diffusion是这样工作的

程序那些事

程序那些事 Stable Diffusion

云实例初始化的行业标准:Cloud-Init

AutoMQ

kafka java

浅析软件开发技术的发展历程与展望

EquatorCoco

软件开发

【案例分享】明道数云为阿联酋迪拜公司Eastman BLDG打造外贸管理系统

明道云

MySQL 导出一条数据的插入语句

不在线第一只蜗牛

MySQL 数据库

软件测试学习笔记丨Vue常用指令-属性绑定

测试人

软件测试 自动化测试 测试开发

你最哪些推荐的 C/C++ 程序库,为什么?

伤感汤姆布利柏

Java工程师的行业的生命周期

秃头小帅oi

AutoMQ 生态集成 Tigris

AutoMQ

Kafk java

性能30%↑|阿里云AnalyticDB*AMD EPYC,数据分析步入Next Level

阿里云瑶池数据库

数据库 阿里云 AMD analyticDB

Docker部署深度学习模型

快乐非自愿限量之名

深度学习 Docker 容器化

多源异构数据融合的必要性、挑战和解决方案

Aloudata

数据分析 数据融合 数据集成 数据虚拟化 Data Fabric

「布道师系列文章」众安保险王凯解析 Kafka 网络通信

AutoMQ

kafka java

OmniFocus Pro 4 for mac(最佳GTD时间效率工具)v4.2.1版

Mac相关知识分享

办公软件 Mac软件 mac办公软件下载

高端录音工作室Loopback for Mac(mac虚拟音频设备)v2.3.3版

Mac相关知识分享

Mac软件 mac软件下载 音频设备软件

基于工业互联网打造敏捷供应链的实现方式:创新路径与实践应用

天津汇柏科技有限公司

工业互联网 敏捷供应链

跟单合约:降低交易门槛的创新工具

dappweb

互联网的下一个飞跃:Web2 和 Web3 解释以及它如何使您受益

区块链开发团队DappNetWork

使用Kubernetes来管理Docker扩展_DevOps & 平台工程_Carlos Sanchez_InfoQ精选文章