当前位置: 首页 > article >正文

Kubernetes无头服务(Headless Service)实战:从DNS解析到跨集群访问

1. 无头服务到底是什么为什么你需要它大家好我是老K在容器和云原生领域摸爬滚打了十来年。今天咱们不聊那些虚的直接上手来聊聊Kubernetes里一个听起来有点“怪”但用起来特别“香”的功能——无头服务Headless Service。很多刚接触K8s的朋友一听到“无头”可能就懵了觉得这玩意儿是不是不完整、有问题其实恰恰相反。你可以把它想象成一个“通讯录”或者“电话黄页”。普通的Kubernetes Service服务就像一个公司的总机电话ClusterIP你打过去总机接线员kube-proxy的负载均衡会帮你转接到某个分机Pod上至于转给谁你控制不了。而无头服务呢它直接把公司里所有员工的分机号码列表Pod IP地址给你。你想找谁谈就直接拨他的分机完全绕过了总机。所以无头服务的核心就两点第一它没有ClusterIP总机号码第二它的DNS查询直接返回后端所有Pod的真实IP地址列表。这带来了几个非常实在的好处直接通信延迟更低少了负载均衡这一层转发网络路径更短对于延迟敏感的应用比如游戏服务器、实时数据处理是福音。客户端自主负载均衡你的应用程序可以自己决定连接哪个Pod。比如你可以实现基于地理位置、当前负载或者特定业务逻辑的智能路由灵活性大增。为有状态应用而生这是它最经典的舞台。像MySQL主从、Redis集群、Kafka、Elasticsearch这些有状态的应用每个Pod都有自己独特的身份和数据。它们需要稳定的网络标识来彼此发现和直接对话而不是通过一个随机分配的代理。无头服务配合StatefulSet就能为每个Pod提供一个形如pod-name.service-name.namespace.svc.cluster.local的、稳定且可预测的DNS名称。简单来说当你需要点对点直连、需要客户端感知所有后端实例、或者正在部署有状态集群应用时无头服务就是你该掏出来的工具。接下来我们就从最核心的DNS解析机制开始一步步把它玩明白。2. 核心机制无头服务的DNS解析到底怎么工作的理解了DNS解析你就掌握了无头服务的灵魂。这里和普通服务的区别非常大咱们得掰开揉碎了讲。2.1 服务级解析拿到全体成员名单当你创建一个普通的Service比如叫my-svcKubernetes会给它分配一个虚拟IPClusterIP。你在集群内通过my-svc.default.svc.cluster.local这个域名做DNS查询时CoreDNS或kube-dns会直接返回这个ClusterIP。但对于无头服务clusterIP: None故事就变了。你用同样的域名my-headless-svc.default.svc.cluster.local去查询CoreDNS不会返回一个单一的IP而是会返回与该服务选择器Selector匹配的所有Pod的IP地址列表A记录。我们来实际操作一下。假设我们有一个无头服务redis-headless它选择了3个带有app: redis标签的Pod。# redis-headless-svc.yaml apiVersion: v1 kind: Service metadata: name: redis-headless spec: clusterIP: None # 关键在这里 selector: app: redis ports: - port: 6379创建服务并运行Pod后在集群内的另一个Pod里执行nslookup# 在集群内任意Pod中执行 nslookup redis-headless.default.svc.cluster.local你可能会看到类似这样的输出Server: 10.96.0.10 Address: 10.96.0.10#53 Name: redis-headless.default.svc.cluster.local Address: 10.244.1.21 Name: redis-headless.default.svc.cluster.local Address: 10.244.2.15 Name: redis-headless.default.svc.cluster.local Address: 10.244.3.33看到了吗它一口气返回了三个IP地址。你的客户端应用比如一个Redis客户端库拿到这个列表后就可以自己实现连接逻辑比如轮询、随机选一个或者尝试连接所有节点来发现集群拓扑。注意这里返回的IP地址顺序不保证稳定每次DNS查询可能顺序不同。如果你的应用依赖稳定的成员列表最好在客户端做排序或缓存。2.2 Pod级解析精准呼叫特定成员服务级解析给了你全员名单但有时候你需要精准定位到某一个特定的Pod。尤其是和StatefulSet结合时每个Pod有自己唯一的、稳定的标识如redis-0,redis-1。无头服务为此提供了强大的Pod级DNS解析。对于StatefulSet管理的PodKubernetes会自动创建格式如下的DNS记录pod-name.service-name.namespace.svc.cluster.local还是上面的Redis例子假设StatefulSet叫redis-cluster无头服务叫redis-headless那么Podredis-cluster-0的域名是redis-cluster-0.redis-headless.default.svc.cluster.localPodredis-cluster-1的域名是redis-cluster-1.redis-headless.default.svc.cluster.local我们来验证一下。在集群内Pod执行nslookup redis-cluster-0.redis-headless.default.svc.cluster.local输出会是Server: 10.96.0.10 Address: 10.96.0.10#53 Name: redis-cluster-0.redis-headless.default.svc.cluster.local Address: 10.244.1.21这个DNS记录是稳定且持久的。即使Podredis-cluster-0发生重启、迁移只要它还是StatefulSet管理的第一个实例这个域名就会一直解析到它当前所在的IP。这对于有状态应用建立主从关系、分片路由比如Shard Key映射到特定Pod至关重要。客户端可以直接用这个稳定的域名与特定的Pod对话完全不需要关心它底层IP是否变化。2.3 实战解析测试从看懂到会查光说不练假把式我们部署一个简单的StatefulSet和无头服务来亲眼看看。下面是一个NGINX的例子虽然NGINX通常无状态但很适合演示。# nginx-statefulset.yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: web spec: serviceName: nginx-headless # 指定关联的无头服务这是StatefulSet的标配 replicas: 2 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:alpine ports: - containerPort: 80 --- # nginx-headless-svc.yaml apiVersion: v1 kind: Service metadata: name: nginx-headless spec: clusterIP: None selector: app: nginx ports: - port: 80应用配置后我们用一个工具Pod比如busybox来进行测试# 运行一个临时busybox Pod kubectl run -it --rm dns-test --imagebusybox:1.28 --restartNever -- sh # 进入容器后测试服务级解析 nslookup nginx-headless.default.svc.cluster.local # 测试Pod级解析 nslookup web-0.nginx-headless.default.svc.cluster.local nslookup web-1.nginx-headless.default.svc.cluster.local通过这个测试你会清晰地看到服务域名返回两个IP而每个Pod域名精确地解析到各自的IP。这就是无头服务DNS魔力的直观体现。3. 黄金搭档无头服务与StatefulSet部署有状态应用如果说无头服务是一把好枪那StatefulSet就是为它量身定制的弹药。这两者结合是Kubernetes上部署有状态集群应用的标准姿势。我经历过好几次用Deployment普通Service部署Redis哨兵模式结果节点发现一团糟的坑换成StatefulSetHeadless Service后世界瞬间清净了。3.1 为什么它们是天作之合StatefulSet为每个Pod提供稳定的、唯一的网络标识Pod名称如kafka-0,kafka-1在生命周期内不变。稳定的、持久的存储通过PVC模板每个Pod都能挂载自己专属的持久化存储卷即使Pod漂移数据也跟着走。有序的部署和扩缩容按顺序启停Pod保证主从应用如MySQL在初始化时的顺序性。而无头服务则为这些具有稳定标识的Pod提供了稳定的DNS发现机制。StatefulSet的serviceName字段指向这个无头服务从而将Pod的稳定名称与可发现的DNS记录绑定在一起。3.2 实战案例部署一个ZooKeeper集群ZooKeeper是一个经典的分布式协调服务集群节点需要彼此感知。我们用它来演示最佳实践。# zookeeper-statefulset.yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: zk spec: serviceName: zk-headless # 关联无头服务 replicas: 3 selector: matchLabels: app: zookeeper template: metadata: labels: app: zookeeper spec: containers: - name: zookeeper image: zookeeper:3.8 ports: - containerPort: 2181 name: client - containerPort: 2888 name: server - containerPort: 3888 name: leader-election env: - name: ZOO_MY_ID valueFrom: fieldRef: fieldPath: metadata.name # 关键从Pod名zk-0,1,2中提取序号作为MyID - name: ZOO_SERVERS value: server.1zk-0.zk-headless:2888:3888;2181 server.2zk-1.zk-headless:2888:3888;2181 server.3zk-2.zk-headless:2888:3888;2181 volumeMounts: - name: datadir mountPath: /data volumeClaimTemplates: # 每个Pod都有自己独立的存储 - metadata: name: datadir spec: accessModes: [ ReadWriteOnce ] resources: requests: storage: 1Gi --- # zookeeper-headless-svc.yaml apiVersion: v1 kind: Service metadata: name: zk-headless spec: clusterIP: None selector: app: zookeeper ports: - port: 2181 name: client - port: 2888 name: server - port: 3888 name: leader-election这个配置的精华在于环境变量ZOO_SERVERS。它利用无头服务生成的Pod级DNS记录为每个ZooKeeper节点配置了完整的集群成员列表。zk-0.zk-headless会稳定解析到第一个Pod。zk-1.zk-headless解析到第二个依此类推。这样无论Pod的IP如何变化只要DNS名称不变ZooKeeper集群就能正确组建。这才是云原生时代有状态应用该有的部署方式——声明式、自发现、高可靠。3.3 你可能遇到的坑与解决方案DNS解析延迟Pod启动后DNS记录可能不是立即生效。在应用启动脚本里最好加入对同伴域名的解析检查比如用dig或nslookup轮询直到解析成功再启动服务进程。客户端连接池如果你的客户端应用使用连接池并且通过无头服务域名获取了一组IP当Pod发生滚动更新时IP列表会变化。客户端需要实现监听DNS变化TTL过期后重新查询或使用支持动态服务器列表的客户端库如某些Redis/MySQL驱动及时更新连接池避免连接到已终止的Pod。Headless Service选择器务必确保Service的selector和StatefulSet Pod的labels精确匹配。一个字母错了服务就找不到PodDNS记录也就不会创建。4. 进阶挑战无头服务如何实现跨集群访问前面的场景都在单个Kubernetes集群内。现在考虑更复杂的场景你的应用微服务部署在集群A而一个由StatefulSet管理的数据库集群部署在集群B。集群A中的应用如何才能像在同一个集群内一样通过无头服务的DNS名称发现并直接连接到集群B中的数据库Pod呢这就是跨集群服务发现的范畴也是无头服务大显身手的进阶舞台。4.1 跨集群访问的难点与思路在单集群内CoreDNS和Kubernetes服务API共同协作维护了svc.cluster.local这个域下的所有记录。但跨集群时网络不互通DNS也不互通。核心思路有两个打通网络让不同集群的Pod网络能够直接路由到对方。这通常通过CNI插件如Cilium Cluster Mesh或专门的网络隧道工具实现。同步服务元数据将一个集群中的服务Service和端点Endpoint信息同步到另一个集群并在目标集群中创建对应的DNS记录让本地Pod能够解析。业界有一些成熟的项目来解决这个问题例如Submariner和Service Mesh如Istio的跨集群方案。这里我们以Submariner为例因为它对无头服务的跨集群支持比较直观。4.2 使用Submariner实现无头服务跨集群Submariner提供了跨集群的网络连接和服务发现。它会在每个成员集群中部署一个组件Broker, Gateway等并建立一个安全的VPN隧道通常是IPSec或WireGuard来连接集群间的Pod网络。假设我们有两个集群cluster-a应用集群和cluster-b数据库集群。我们在cluster-b中部署了上文中的zkStatefulSet 和zk-headlessService。步骤一安装并连接Submariner首先在两个集群上安装Submariner并通过Broker完成集群对接。这个过程涉及subctl命令行工具具体安装步骤可参考官方文档。完成后两个集群的Pod网络理论上应该可以互通。步骤二导出Export无头服务在数据库所在的cluster-b中我们需要将无头服务zk-headless导出使其能被cluster-a发现。# 在 cluster-b 的上下文中执行 subctl export service --namespace default zk-headless这个命令会创建一个ServiceExport资源。Submariner会监听到这个资源并将该服务的信息同步到Broker。步骤三导入Import并发现服务在应用所在的cluster-a中Submariner的组件会从Broker获取到被导出的服务信息并自动创建对应的ServiceImport资源。同时Submariner会修改集群的DNS配置通常是CoreDNS添加新的域名后缀默认为.clusterset.local。步骤四跨集群DNS解析测试现在在cluster-a的任何一个Pod里你可以通过新的域名来解析cluster-b中的无头服务了。解析整个服务获取所有Pod IPnslookup zk-headless.default.svc.clusterset.local这应该会返回cluster-b中所有ZooKeeper Pod的IP地址来自集群B的Pod CIDR网段。解析特定Podnslookup zk-0.zk-headless.default.svc.clusterset.local你也可以使用包含源集群ID的完整域名格式nslookup zk-0.cluster-b.zk-headless.default.svc.clusterset.local步骤五应用连接你的应用在cluster-a中现在就可以像连接本地服务一样使用zk-headless.default.svc.clusterset.local这个域名来连接cluster-b中的ZooKeeper集群了。因为网络层已经被Submariner打通TCP连接可以直接建立。4.3 其他方案与注意事项除了SubmarinerIstio的多集群模式通过共享控制平面或联邦也能实现跨集群的服务发现和通信它对无头服务的支持需要仔细配置ServiceEntry和DestinationRule。Cilium Cluster Mesh则更侧重于网络层的直接打通配合原生的Kubernetes服务发现机制。在实施跨集群无头服务访问时务必注意网络性能跨集群流量经过隧道封装会带来额外的延迟和开销需评估是否满足业务要求。安全策略确保跨集群的网络策略如CiliumNetworkPolicy, Calico GlobalNetworkPolicy配置正确只开放必要的端口。DNS TTL跨集群DNS记录的TTL设置可能影响故障转移速度。端点健康检查确保源集群能够正确感知目标集群Pod的健康状态及时从DNS记录中移除不可用的端点。跨集群访问是无头服务应用的深水区但它解锁了混合云、多地域部署等复杂架构的可能性。当你需要将分布在不同Kubernetes集群中的有状态应用组件连接成一个统一的服务网格时这套组合拳的价值就凸显出来了。

相关文章:

Kubernetes无头服务(Headless Service)实战:从DNS解析到跨集群访问

1. 无头服务到底是什么?为什么你需要它? 大家好,我是老K,在容器和云原生领域摸爬滚打了十来年。今天咱们不聊那些虚的,直接上手,来聊聊Kubernetes里一个听起来有点“怪”,但用起来特别“香”的功…...

雷电模拟器4抓包全攻略:从Charles配置到HTTPS解密(附证书避坑指南)

移动端应用深度调试:构建本地HTTPS流量分析环境实战指南 在移动应用开发与安全测试的日常工作中,能否清晰地洞察应用与服务器之间的每一次“对话”,往往决定了问题排查的效率与深度。无论是为了优化一个API的响应速度,还是逆向分析…...

Leaflet室内导航实战:如何用PathFinding.js避开障碍物规划最优路线

Leaflet室内导航实战:用PathFinding.js构建高精度避障路径规划系统 你是否曾在一个大型购物中心里迷失方向,或者在医院复杂的科室走廊中来回打转?对于开发者而言,构建一个能精准应对这些室内复杂环境的导航系统,远比处…...

CFA一级2025年备考:以Kaplan Notes为核心的高效自学路线图

1. 为什么选择Kaplan Notes作为备考核心? 如果你正准备2025年的CFA一级考试,并且打算自学,那你肯定纠结过一个问题:面对官方那几本厚得像砖头一样的教材,我到底该怎么啃?作为一个过来人,我当年备…...

Windows环境快速部署Nacos-Server 2.4.0.1及MySQL配置详解

1. 为什么选择在Windows上部署Nacos? 如果你是一名Java或微服务开发者,那么Nacos这个名字你一定不陌生。它就像是微服务世界里的“通讯录”和“配置中心”合体,服务注册、发现、配置管理,它一手包办。但很多官方教程和社区分享&a…...

MongoDB分片集群实战:从零搭建高可用分布式数据库

1. 为什么你需要一个MongoDB分片集群? 如果你正在读这篇文章,我猜你大概率已经遇到了单台MongoDB服务器的瓶颈。可能是磁盘空间快满了,加硬盘也解决不了根本问题;也可能是查询速度越来越慢,即使加了索引,面…...

SpringBoot整合Quartz实战:从建表到动态任务管理

1. 为什么你需要Quartz?从“一次性”到“动态化”的调度进化 如果你用过SpringBoot自带的Scheduled注解,那你肯定知道它有多方便。加个注解,配个cron表达式,任务就能定时跑了。但用久了,痛点就来了:所有任务…...

Questasim 10.6c 从零安装到环境配置:避坑指南与实战步骤

1. 环境准备:万事开头,细节决定成败 大家好,我是老张,在芯片设计和验证这行摸爬滚打了十几年,用过的仿真工具能摆满一桌子。今天咱们不聊复杂的验证方法学,就踏踏实实地解决一个最基础、也最容易让人“从入…...

微信自动化机器人

在私域运营中,用户关系是企业最重要的资产,但运营者的时间和精力不应被重复、低效的工作消耗。针对微信生态下常见的运营瓶颈,GeWe 开放平台提供一套智能、闭环的解决方案,让企业在客户互动、社群管理和业务增长上实现质的跃升。智…...

RAFT:领域特定RAG的LLM适配配方

RAFT:领域特定RAG的LLM适配配方 【免费下载链接】gorilla Gorilla: An API store for LLMs 项目地址: https://gitcode.com/gh_mirrors/go/gorilla RAFT(Retrieval Aware Fine-Tuning)是一种专门针对领域特定RAG(检索增强生…...

逆向快手:通过Xposed强制QUIC降级为HTTPS实现抓包

1. 为什么抓不到快手的包?聊聊QUIC这个“拦路虎” 如果你最近尝试过用Charles或者Fiddler去抓取快手App的网络请求,大概率会一脸懵:怎么除了几个零星的无用请求,核心的API数据一个都看不到?我之前也卡在这个问题上很久…...

若依Cloud+Flowable6.7.2实战:手把手教你搭建微服务工作流模块(附避坑指南)

若依Cloud微服务架构下Flowable工作流模块的深度集成与实战避坑指南 在当今企业级应用开发中,业务流程的自动化与管理已成为提升运营效率的核心环节。对于已经采用若依Cloud(RuoYi-Cloud)这一成熟微服务架构的团队而言,引入一个稳…...

终极指南:如何高效使用 sebastian/object-enumerator 遍历对象与数组结构

终极指南:如何高效使用 sebastian/object-enumerator 遍历对象与数组结构 【免费下载链接】object-enumerator Traverses array structures and object graphs to enumerate all referenced objects 项目地址: https://gitcode.com/gh_mirrors/ob/object-enumerat…...

7步快速参与Git-Stats开源项目开发:新手友好的社区贡献指南

7步快速参与Git-Stats开源项目开发:新手友好的社区贡献指南 【免费下载链接】git-stats 🍀 Local git statistics including GitHub-like contributions calendars. 项目地址: https://gitcode.com/gh_mirrors/gi/git-stats Git-Stats是一个强大的…...

10个HTML DOM文本选择技巧:获取选中内容和方向判断的终极指南

10个HTML DOM文本选择技巧:获取选中内容和方向判断的终极指南 【免费下载链接】html-dom Common tasks of managing HTML DOM with vanilla JavaScript. Give me 1 ⭐if it’s useful. 项目地址: https://gitcode.com/gh_mirrors/ht/html-dom HTML DOM文本选…...

如何构建安全高效的FBCTF会话管理系统:用户状态保持与安全控制完整指南

如何构建安全高效的FBCTF会话管理系统:用户状态保持与安全控制完整指南 【免费下载链接】fbctf 项目地址: https://gitcode.com/gh_mirrors/fbc/fbctf FBCTF(Facebook CTF)是一款功能强大的开源CTF平台,其会话管理系统是保…...

N体引力模拟终极指南:如何在DirectX-Graphics-Samples中实现高性能物理计算与渲染

N体引力模拟终极指南:如何在DirectX-Graphics-Samples中实现高性能物理计算与渲染 【免费下载链接】DirectX-Graphics-Samples This repo contains the DirectX Graphics samples that demonstrate how to build graphics intensive applications on Windows. 项目…...

Symfony Translation终极缓存策略对比:TTL vs LRU vs 写入时失效

Symfony Translation终极缓存策略对比:TTL vs LRU vs 写入时失效 【免费下载链接】translation symfony/translation: 是一个用于 PHP 的翻译库,支持多种消息源和翻译格式,可以用于构建多语言的 Web 应用程序和 API。 项目地址: https://gi…...

终极性能优化指南:如何使用cProfile深度分析ngxtop日志解析瓶颈

终极性能优化指南:如何使用cProfile深度分析ngxtop日志解析瓶颈 【免费下载链接】ngxtop Real-time metrics for nginx server 项目地址: https://gitcode.com/gh_mirrors/ng/ngxtop ngxtop作为一款实时Nginx服务器 metrics工具,能够帮助开发者实…...

如何为AndroidAssetStudio配置高效GitHub Actions持续集成:开发者必备指南

如何为AndroidAssetStudio配置高效GitHub Actions持续集成:开发者必备指南 【免费下载链接】AndroidAssetStudio romannurik/AndroidAssetStudio: AndroidAssetStudio是一个在线工具集,可以帮助开发者快速生成适合不同屏幕密度和设备方向的Android应用图…...

快速绘制数据集终极指南:创意编程与Processing、p5.js集成教程

快速绘制数据集终极指南:创意编程与Processing、p5.js集成教程 【免费下载链接】quickdraw-dataset Documentation on how to access and use the Quick, Draw! Dataset. 项目地址: https://gitcode.com/gh_mirrors/qu/quickdraw-dataset Quick, Draw! Datas…...

Pendulum完全指南:10个技巧告别Python datetime的烦恼

Pendulum完全指南:10个技巧告别Python datetime的烦恼 【免费下载链接】pendulum Python datetimes made easy 项目地址: https://gitcode.com/gh_mirrors/pe/pendulum Pendulum是一个让Python datetime操作变得简单的强大库,它解决了原生datetim…...

LoRA Diffusion生态系统与最佳实践

LoRA Diffusion生态系统与最佳实践 【免费下载链接】lora Using Low-rank adaptation to quickly fine-tune diffusion models. 项目地址: https://gitcode.com/gh_mirrors/lora2/lora LoRA Diffusion项目与HuggingFace Diffusers库的深度集成为用户提供了无缝的模型微调…...

模型管理与优化:LoRA权重转换与蒸馏技术

模型管理与优化:LoRA权重转换与蒸馏技术 【免费下载链接】lora Using Low-rank adaptation to quickly fine-tune diffusion models. 项目地址: https://gitcode.com/gh_mirrors/lora2/lora 本文深入探讨了LoRA(Low-Rank Adaptation)技…...

哪吒探针Windows/Linux双平台安装避坑指南:从环境变量到systemd全流程解析

哪吒探针Windows/Linux双平台安装避坑指南:从环境变量到systemd全流程解析 如果你同时管理着Windows和Linux服务器,并且正在寻找一个轻量、美观又能统一监控的方案,哪吒探针很可能已经进入了你的视野。它确实是个好东西,开源、功能…...

LoRA模型推理与应用:生成高质量定制化图像

LoRA模型推理与应用:生成高质量定制化图像 【免费下载链接】lora Using Low-rank adaptation to quickly fine-tune diffusion models. 项目地址: https://gitcode.com/gh_mirrors/lora2/lora 本文深入探讨了LoRA(Low-Rank Adaptation&#xff09…...

LoRA Diffusion实战:从零开始训练你的第一个风格模型

LoRA Diffusion实战:从零开始训练你的第一个风格模型 【免费下载链接】lora Using Low-rank adaptation to quickly fine-tune diffusion models. 项目地址: https://gitcode.com/gh_mirrors/lora2/lora 本文详细介绍了LoRA Diffusion模型训练的全流程&#…...

5步打造完美应用图标:AndroidAssetStudio与Capacitor集成终极指南

5步打造完美应用图标:AndroidAssetStudio与Capacitor集成终极指南 【免费下载链接】AndroidAssetStudio romannurik/AndroidAssetStudio: AndroidAssetStudio是一个在线工具集,可以帮助开发者快速生成适合不同屏幕密度和设备方向的Android应用图标与启动…...

7个实用技巧掌握Flight混入机制:轻松扩展JavaScript组件功能

7个实用技巧掌握Flight混入机制:轻松扩展JavaScript组件功能 【免费下载链接】flight A component-based, event-driven JavaScript framework from Twitter 项目地址: https://gitcode.com/gh_mirrors/fl/flight Flight是Twitter开发的组件化、事件驱动Java…...

终极指南:AndroidAssetStudio与PhoneGap集成制作专业移动应用图标

终极指南:AndroidAssetStudio与PhoneGap集成制作专业移动应用图标 【免费下载链接】AndroidAssetStudio romannurik/AndroidAssetStudio: AndroidAssetStudio是一个在线工具集,可以帮助开发者快速生成适合不同屏幕密度和设备方向的Android应用图标与启动…...