☰
Calico v3.25.0离线包制作与导入:解决离线K8s网络插件难题
2026/10/13 7:12:01 网站建设 项目流程

简介:Calico 3.25.0离线包是一套完整的Kubernetes网络组件部署资源,主要面向集群管理员、运维人员以及需要在断网或隔离环境中搭建集群的技术人员。离线安装是应对在线拉取镜像失败的有效手段,本包将所有依赖镜像和配置进行封装,实现无需外部网络即可完成插件安装。压缩包内共有三个文件,整体大小约一百八十七点二四兆字节,其中tar镜像压缩文件用于导入核心组件,yaml编排清单用于定义部署结构,txt说明文档则提供安装指引。当前已有四百五十三人学习或下载,适合用于内网生产环境、离线测试与教学演示。借助该离线包,可以快速部署Calico并启用网络策略,同时加深对镜像导入、组件启动及配置项的理解,为后续集群排障和维护提供有效支撑。这是一个兼具实用性与学习价值的资源包。

1. calico-image-v3.25.0离线包到底解决的什么场景

做过内部机房交付的人都有这种经历:Kubernetes集群的Pod网段明明配好了,节点却一直NotReady,kube-system下面全是ContainerCreating。查了半天,真正的缺口往往只有一个——容器网络插件Calico的镜像根本拉不下来。在没有外网访问条件的离线环境里,只要提前把Calico相关镜像准备好,再打包搬运到目标节点,问题就能解决,这也是calico-image-v3.25.0离线包存在的意义。

这篇文章不绕弯子,直接按做离线包的实际流程来拆:先讲清楚v3.25.0离线包里需要哪些镜像,再给出从拉取、导出、压缩到导入的完整操作,最后把我在docker和containerd两种运行时里踩过的坑一条条摆出来。适合正在维护内网K8s集群、需要给客户交付网络插件,或者自己搭离线实验环境的工程师。看完你至少能独立产出一个能用的v3.25.0离线包,并且知道导入后怎么验证、出了问题看哪里。

2. v3.25.0离线包有什么:镜像组成与部署方式先理清

做离线包最忌讳拿到一个别人给的tar就直接传,因为Calico的镜像清单和部署方式强相关。先用几分钟把清单和架构理清,后面能省掉大把排障时间。这一章的核心任务,是让你在动手拉镜像之前,知道自己到底在打包什么。

2.1 Calico v3.25.0的镜像组成:一份典型的images.txt长这样

Calico在Kubernetes上不是一个单体组件,而是由几个容器分工协作。理解每个镜像的作用,才知道离线包里的镜像能不能删、怎么删。下面的表是v3.25.0默认部署里最常见的几个镜像。

镜像名作用是否默认必装
calico/node每台节点上的agent,负责路由、隧道、访问控制策略必装
calico/kube-controllers控制器,监听Pod、Node、策略资源并做调谐必装
calico/cniCNI插件二进制和安装脚本,负责把插件放进宿主机必装
calico/pod2daemon-flexvolFlexVolume驱动,用于挂载Calico运行所需的证书和配置启用flexvol时需要
calico/typha中间层组件,帮node减轻对API Server的压力大规模集群推荐
calico/apiserver可选的API服务,用于网络策略管理界面集成默认不装

自己动手做离线包的第一步,是从Calico的manifest文件里提取image字段,而不是凭记忆手写清单。假设你已经下载了v3.25.0对应的calico.yaml,用下面这条命令可以拿到全部镜像名:

# 从calico.yaml中提取所有image字段,去掉重复项 grep -oE 'image: [^ ]+' calico.yaml | awk '{print $2}' | sort -u

这段命令的逻辑很简单:grep用正则匹配image:后面到第一个空格的内容,awk取第二列就是镜像名,sort -u去重。需要注意,如果manifest里镜像写的是image: calico/node:v3.25.0,提取结果就是带tag的全名。如果写的是带@sha256:...的digest形式,提取结果里会带着一长串digest,这种包在导入时校验更严格,也更容易遇到平台不匹配问题。我一般建议离线包优先保留普通tag,digest形式适合对供应链安全要求极高的环境,但操作成本明显更高。

2.2 按部署方式定内容:manifest安装和Operator安装是两套镜像集

Calico v3.25.0支持两种主流部署方式:一种是直接应用manifest,另一种是用Operator编排。同样的版本,离线包内容差很多。如果用manifest方式,默认就是上面表格里那几个镜像,包小、逻辑简单;如果用Operator方式,还需要额外打包operator、csi等相关镜像,包体积变大,排障链路也变长。

个人习惯是:除非团队已经有成熟的Operator运维体系,否则离线交付优先选manifest方式。原因有三个。第一,manifest方式直接暴露所有核心组件,出问题看Pod日志就能定位;第二,离线包小,传输压力低;第三,升级时只需要换镜像tag,不需要处理Operator自身的版本兼容。如果你确实用了Operator,那么打包时要检查kubectl get deploy -n calico-system里出现的所有镜像,不能只照着网上流传的清单来。

给一张对比表,帮助你决定用哪种方式:

部署方式镜像数量管理复杂度离线包体积适合场景
manifest约4-6个低小中小集群、交付交付
operator约8-10个高大大规模集群、自动化运维

这个版本选择直接决定了后面打包的镜像列表,所以不要跳过。

2.3 打包前的版本和架构核对:离线包最容易在这里翻车

镜像清单确认后,还要确认两件事:目标集群的Kubernetes版本是否在Calico v3.25.0的支持范围内,以及目标节点的CPU架构是什么。Kubernetes版本太老,Calico用的API字段可能不识别;太新,某些行为又可能被废弃。这部分以官方兼容矩阵为准,不要凭感觉。

打包机上先跑下面这些命令,把环境信息记下来:

# 确认打包机的CPU架构 uname -m # 确认目标Kubernetes集群版本 kubectl version --output=jsonpath='{.serverVersion.gitVersion}' # 确认目标节点可能用的运行时版本 docker version --format '{{.Server.Version}}' ctr version

说明一下参数:uname -m输出x86_64、aarch64这类信息,如果是x86_64,后面docker pull会默认拉amd64平台镜像;如果目标节点是arm64,导入后大概率直接exec format error。kubectl version的output参数是为了拿到干净的版本号,方便你对照兼容表。ctr version用来确认containerd存在,后面决定用docker load还是ctr import。

这一步不做,后面所有步骤都可能白做。尤其是异构集群,我见过有人把amd64的tar传到arm64节点,load完后一启动就崩,浪费一整晚。

2.4 拉取镜像前,还要决定离线包怎么落地

Calico镜像拉到本地之后,你有两条落地路径。一条是把镜像导出成tar,再一台台导入到目标节点本地运行时;另一条是推送到内网私有镜像仓库,所有节点统一从内网仓库拉取。离线包这个标题通常指前者,但实际交付中后者也很常见。

如果走tar路线,拉镜像时保持原始tag即可,导入后镜像名和manifest里对得上,kubelet在imagePullPolicy为IfNotPresent时不会尝试外连。如果走内网仓库路线,拉完镜像后要把每个镜像重打tag成内网仓库地址,然后统一push。比如:

# 把镜像重打tag为内网仓库地址 docker tag calico/node:v3.25.0 registry.local/calico/node:v3.25.0 docker push registry.local/calico/node:v3.25.0

这里registry.local是内网镜像仓库地址的占位,实际替换成你们的仓库域名和端口。push之后,节点在创建Pod时会从内网仓库拉取,离线环境完全可行。考虑到标题是离线包,后面章节以tar方式为主线,但你要知道还有仓库这条更省事的路,最后一章再展开。

3. 做包的实操:拉镜像、存tar、压缩、校验一条龙

清单理清,环境确认完,这一步就是把镜像真正变成可以搬运的文件。整个过程分四步:拉镜像、重打tag或保持原样、docker save、压缩校验。每一小步都有值得注意的细节。

3.1 用循环脚本拉全镜像,避免手敲漏项

镜像数量看起来不多,但手敲docker pull容易漏,尤其是有typha和apiserver的部署。我习惯把images.txt当作唯一事实来源,再写一个循环去读它。下面是常用脚本:

#!/bin/bash # 从images.txt拉取所有镜像 # images.txt每一行一个镜像名,支持#注释和空行 while read -r image; do # 跳过注释行和空行 if [[ "$image" =~ ^#.* ]] || [[ -z "$image" ]]; then continue fi echo "Pulling: $image" docker pull "$image" done < images.txt

解释一下几个关键点:read -r防止镜像名里的反斜杠被转义;[[ "$image" =~ ^#.* ]]用来跳过以#开头的注释行;continue让循环继续处理下一行而不是退出。echo条打印当前拉的镜像,方便你观察是否卡住。这个脚本的容错性比docker pull $(cat images.txt)好,后者一旦images.txt里有注释或换行符,shell会把整行当成一个参数传给docker,直接报错。

拉镜像期间如果某个镜像特别大,不要急,等待即可。拉完后可以做一次快速对账:

# 统计images.txt里有多少个非注释镜像 grep -vc '^#' images.txt # 统计本地已存在多少Calico镜像 docker images | grep calico | wc -l

如果两个数字对不上,说明有镜像没拉全,先排查再继续。

3.2 docker save的两种写法:固定列表 vs 全量文件列表

镜像拉全后,用docker save导出tar。这里有两种写法,各有适用场景。

第一种是显式列出所有镜像名,适合镜像数量少、列表稳定的情况:

# 显式导出4个核心镜像到一个tar docker save -o calico-images-v3.25.0.tar \ calico/node:v3.25.0 \ calico/kube-controllers:v3.25.0 \ calico/cni:v3.25.0 \ calico/pod2daemon-flexvol:v3.25.0

第二种是从images.txt读取,适合镜像多、或者你希望脚本可复用:

# 从文件读取镜像列表,去掉注释行后交给docker save docker save -o calico-images-v3.25.0.tar $(grep -v '^#' images.txt)

参数说明:-o指定输出文件,会生成一个包含所有镜像层的tar归档。$(grep -v '^#' images.txt)用命令替换把非注释行展开成参数列表。这种写法在镜像数量几十个以内没问题,如果上百个,命令行参数可能超长,到时候再改成逐镜像追加。

还有一个容易忽略的点:docker save导出的是当前机器架构对应的镜像。你在amd64机器上docker pull calico/node:v3.25.0,得到的是linux/amd64平台镜像,save到tar里也只会是这个平台。如果你需要同时给arm64节点用,就必须在打包机上用docker pull --platform linux/arm64再拉一份arm64镜像,然后单独save一个tar,不能指望一个tar通吃。

3.3 压缩离线包:用xz还是gzip、校验文件怎么放

原始tar文件体积通常在几百MB到1GB以上,直接放到U盘或走网线传输压力大。我一般用xz压缩,压缩率比gzip更高,尤其对docker镜像这种重复层比较多的大文件效果明显。命令如下:

# 压缩tar为xz格式,-k表示保留原tar xz -k -T0 calico-images-v3.25.0.tar # 生成SHA256校验文件 sha256sum calico-images-v3.25.0.tar.xz > calico-images-v3.25.0.tar.xz.sha256

参数说明:-k在压缩完成后保留原始tar文件,方便你对比;-T0让xz使用所有CPU核心压缩,速度会快很多,但CPU会短暂打满。sha256sum生成的校验文件内容是“哈希值 文件名”,后面传输到目标节点后可以直接用sha256sum -c验证。如果磁盘空间紧张,可以在确认xz文件完整后手动删掉原始tar;但建议至少在做完传输前的本地校验前保留。

磁盘预算要给足:docker本身存储镜像要占一份空间,save出来的tar是一份,xz压缩文件又是一份,所以打包目录至少准备镜像总大小的3倍剩余空间。目标节点导入时也需要类似空间,因为解压和导入是同时发生的。

3.4 把离线包送进离线环境:scp、内部共享目录和U盘的取舍

包做出来后,传输方式要根据现场条件选择。节点少,直接scp或U盘拷贝最直接;节点多,用内部共享目录加脚本批量拉取更高效。

# 从打包机传到目标节点,假设目标节点是10.0.0.5 scp calico-images-v3.25.0.tar.xz calico-images-v3.25.0.tar.xz.sha256 user@10.0.0.5:/opt/calico-pkg/

scp的源文件一次传两个,路径由你决定。传输完,到目标节点上先做校验:

# 进入包所在目录,校验文件完整性 cd /opt/calico-pkg sha256sum -c calico-images-v3.25.0.tar.xz.sha256

输出出现OK,说明文件没损坏;输出FAILED,就要重新拷贝。这一步绝对不能跳过,U盘拷贝、网络传输都可能导致文件损坏,导入时报unexpected EOF再查就晚了。如果是大批量节点,不要对每台都scp,更好的办法是把包放到一台中转入口,挂出一个共享目录,其他节点从共享目录直接读取;或者先把包传到一台节点,再用内网广播的方式分发。传输工具不限,但校验动作必须一致。

4. 导入目标节点并让Kubernetes认账:docker load、ctr import与crictl检查

离线包传到目标节点,导入只是第一步,真正的难点是让kubelet通过CRI看到这些镜像。Calico的Pod由kubelet创建,kubelet只认CRI接口后面的镜像列表,不认docker images。所以这一章重点解决“导入后怎么让Kubernetes认账”的问题。

4.1 先确认节点用的是docker还是containerd运行时

Kubernetes从1.24开始默认使用containerd作为运行时,但老旧集群或某些发行版里仍然用docker。导入命令完全不同,先搞清楚运行时能省去大量无效操作。

# 用crictl探测当前CRI运行时类型 crictl info --output go-template --template '{{.runtimeType}}' 2>/dev/null || echo "docker"

这段命令的逻辑是:如果系统里装了crictl且能连上CRI socket,就会输出runtimeType,例如containerd;如果crictl不存在或连接失败,命令返回非0,就回退输出docker。更保险的做法是同时看socket文件和systemd服务:

# 检查containerd socket是否存在,以及服务是否在运行 ls -l /var/run/containerd/containerd.sock 2>/dev/null systemctl is-active containerd docker 2>/dev/null

注意systemctl is-active可以一次查多个服务,它会输出每个服务的状态。如果两行输出分别是active和inactive,说明两个进程都存在,但kubelet最终用的是配置的CRI端点。对于离线包导入,我们只需要和CRI对齐,所以下一步用对应的导入命令。

4.2 使用docker load导入镜像,并验证镜像列表

如果节点仍然由docker提供CRI兼容层,也就是Kubernetes通过dockershim调用docker,那么导入直接用docker load即可。这也是老版本K8s最顺滑的方式。

# 解压xz包,-k保留原压缩包,避免重复解压 xz -dk calico-images-v3.25.0.tar.xz # 导入tar到docker docker load -i calico-images-v3.25.0.tar # 查看Calico镜像是否完整 docker images | grep -E "calico.*v3.25.0"

参数说明:-d是decompress,-k保留压缩包;如果你确认不再需要压缩包,可以去掉-k来节省空间。docker load执行成功后,每一行会输出Loaded image: calico/node:v3.25.0,这就是导入成功的信号。最后一条grep命令查看REPOSITORY和TAG两列,如果某个镜像名显示<none>,说明打包时镜像tag丢失或dangling,最好回到打包机重新save。

这里有个容易误解的地方:即使docker images能看到,kubelet如果没有配置dockershim也不一定认。所以还需要用crictl确认,下一节会讲。

4.3 纯containerd环境:用ctr导入到k8s.io命名空间

如果目标节点是纯containerd,docker load就用不上了。要用ctr导入,而且必须指定正确的namespace。

# 将tar导入containerd的k8s.io命名空间 ctr -n k8s.io images import calico-images-v3.25.0.tar # 验证kubelet能否看到镜像 crictl images | grep calico

解释一下这个关键参数:-n k8s.io指定containerd的命名空间为k8s.io,这是CRI插件的默认命名空间,kubelet只从这个命名空间读取镜像。如果你不写-n,默认会导入到default命名空间,ctr自己能看到,crictl和kubelet都看不到。这是离线包导入里最高频的坑,也是为什么我单独拿出来强调。

导入过程中如果镜像tar里带digest,containerd可能会做校验,输出比较慢,属正常现象。导入完成后,crictl images应该能列出calico镜像。如果crictl images看不到但ctr -n k8s.io images list能看到,多半是crictl连接的socket不对,可以用crictl -r unix:///run/containerd/containerd.sock images指定socket再试。

4.4 导入后应用manifest,验证calico-node从Pending到Running

镜像导入只是前置条件,Calico网络插件真正生效要靠manifest里的DaemonSet和控制器。导入完成后,直接在目标集群应用Calico资源:

# 应用Calico的manifest文件 kubectl apply -f calico.yaml # 查看calico-node Pod状态 kubectl get pods -n kube-system -l k8s-app=calico-node -o wide # 查看节点是否Ready kubectl get nodes

如果Pod一直是Pending,先看有没有调度问题;如果Pod在Running和CrashLoopBackOff之间反复,用下面的命令看日志:

# 查看Calico-node日志,定位启动失败原因 kubectl logs -n kube-system -l k8s-app=calico-node --tail=50

日志是最直接的反馈源。常见问题包括CNI二进制没装到宿主机、felix启动失败、BGP配置冲突等,这些在日志里都会有具体关键字。跨节点验证可以找一个已经存在的Pod,进容器ping另一个节点的Pod IP:

# 在Pod里ping其他Pod IP,确认网络通 kubectl exec -it <pod-name> -n <namespace> -- ping <other-pod-ip>

注意<pod-name>和<namespace>要替换成实际值,<other-pod-ip>可以从kubectl get pods -o wide拿到。这一步验证不通过,说明网络策略或路由还有问题,要回去看Calico配置。

5. 避坑:v3.25.0离线包最容易让人返工的5个问题

这一章所有内容都来自我实际做离线交付时踩过又填平的坑。每条按现象、原因、解决来写,直接对照你遇到的问题。

5.1 镜像导入了,Pod还是ImagePullBackOff

现象:crictl images能看到calico镜像,但创建的Calico Pod一直ImagePullBackOff,Events里写着Failed to pull image "calico/node:v3.25.0"。

原因:最常见的是镜像导入到了containerd的default命名空间,而不是k8s.io。其次是manifest里的imagePullPolicy被改成了Always,kubelet会无视本地镜像存在,强制尝试拉取。第三种情况是manifest里的tag和导入的tag不一致,比如导入的是v3.25.0,Pod里写的是latest。

解决:用ctr -n k8s.io images import重新导入镜像到正确命名空间;修改manifest里的imagePullPolicy为IfNotPresent;确保Pod里的镜像tag和你导入的完全一致,一个字符都不能差。验证时用crictl images而不是docker images,因为kubelet只看CRI这一层。

5.2 节点是arm64,导入了amd64的tar,容器起不来

现象:容器启动后马上退出,Event或日志里出现exec format error,然后CrashLoopBackOff。

原因:docker pull在x86_64打包机上默认拉取linux/amd64平台的镜像,save出来的tar也只能是amd64。把这份tar导入arm64节点,CPU无法执行amd64二进制,自然起不来。

解决:打包前确认目标节点架构。如果集群里有arm64节点,必须在任意一台arm64机器上执行docker pull和save,或者用docker pull --platform linux/arm64拉取指定平台镜像:

# 在amd64打包机上拉取arm64平台镜像 docker pull --platform linux/arm64 calico/node:v3.25.0 docker pull --platform linux/arm64 calico/cni:v3.25.0 # 导出成独立tar,命名上标明平台 docker save -o calico-images-v3.25.0-arm64.tar \ calico/node:v3.25.0 \ calico/kube-controllers:v3.25.0 \ calico/cni:v3.25.0 \ calico/pod2daemon-flexvol:v3.25.0

这里的--platform linux/arm64会让docker主动拉取arm64平台manifest;保存时tar里的镜像层就对应arm64。如果集群同时有amd64和arm64节点,就分别做两个tar,不要想用一份包覆盖。

5.3 calico-node频繁重启,日志里出现CNI install失败

现象:calico-node Pod能Running,但每30秒重启一次,日志里出现Unable to locate CNI installation files或类似错误。

原因:Calico的CNI插件不是由calico-node容器负责安装的,而是依赖calico/cni镜像启动一个初始化容器,把二进制写到宿主机的/opt/cni/bin目录。如果你打包时漏掉了calico/cni镜像,或者宿主机该目录没有写权限,SELinux阻止容器写入,安装就会失败。

解决:先确认离线包里包含了calico/cni镜像,再检查宿主机目录权限:

# 查看CNI目录是否存在,以及calico插件有没有被写进去 ls -ld /opt/cni/bin stat -c %y /opt/cni/bin/calico 2>/dev/null

stat -c %y会输出calico二进制的最新修改时间,如果文件不存在,说明安装脚本根本没执行成功。临时排障可以放宽目录权限:chmod 777 /opt/cni/bin,但生产环境不建议,最好调整Pod的SecurityContext或宿主机的SELinux策略,给容器最小化写入权限。

5.4 containerd导入成功,但crictl images依然看不到镜像

现象:用ctr -n k8s.io images import导入tar,提示成功,但紧接着crictl images | grep calico没有任何输出。

原因:crictl默认连接的CRI socket和目标containerd不一定是同一个。有些节点装了多个容器运行时,或者/etc/crictl.yaml里配置的endpoint指向了别的socket,导致crictl看到的镜像列表和kubelet实际用的不一致。

解决:显式指定crictl的socket地址再查:

# 指定containerd的CRI socket验证 crictl -r unix:///run/containerd/containerd.sock images | grep calico

如果这样能看到镜像,说明/etc/crictl.yaml配置有问题,修正里面的endpoint即可。如果依然看不到,再用ctr -n k8s.io images list对比,确认镜像tag是否完整。这条的排查逻辑是:先确认运行时是哪个socket,再确认namespace,最后确认tag。

5.5 解压后的tar在导入时出现unexpected EOF或layer missing

现象:docker load -i calico-images-v3.25.0.tar执行到一半,报Error processing tar file: unexpected EOF,或者导入完成后某个镜像层缺失。

原因:传输过程中xz文件损坏,或者导入节点磁盘空间不足,导致解压出来的tar被截断。也可能是U盘拷贝时文件系统缓存没刷干净。

解决:导入前必须先做两件事:

# 校验xz包哈希 sha256sum -c calico-images-v3.25.0.tar.xz.sha256 # 检查目标空间是否足够 df -h /var/lib/docker /run/containerd

sha256sum -c会读校验文件里的哈希,和当前文件对比,输出OK才继续。df -h检查docker和containerd的数据目录剩余空间,一般至少要预留镜像包大小2倍以上,因为导入过程需要临时解包。如果校验失败,只能重新传输,不要赌运气。

6. 把导入过程固化成脚本:一份顺手可用的calico离线加载工具

到这一步,完整的离线包你已经做出来了,导入流程也走通了。最后我建议把导入动作固化成一个脚本,避免下次换了目标节点又重复踩坑。下面这个脚本是我习惯用的最小可用版本:

#!/bin/bash # calico-load.sh - 导入calico-image-v3.25.0离线包 # 用法: ./calico-load.sh [镜像tar] [运行时] set -euo pipefail ARCHIVE="${1:-calico-images-v3.25.0.tar}" RUNTIME="${2:-auto}" # 自动探测运行时类型 if [ "$RUNTIME" = "auto" ]; then if crictl info --output go-template --template '{{.runtimeType}}' 2>/dev/null | grep -qi containerd; then RUNTIME=containerd else RUNTIME=docker fi fi # 导入镜像 if [ "$RUNTIME" = "docker" ]; then echo ">>> docker load: $ARCHIVE" docker load -i "$ARCHIVE" else echo ">>> ctr import: $ARCHIVE" ctr -n k8s.io images import "$ARCHIVE" fi # 验证镜像 echo "=== 验证Calico镜像 ===" if command -v crictl >/dev/null 2>&1; then crictl images 2>/dev/null | grep calico || docker images | grep calico else docker images | grep calico fi

脚本逻辑不复杂,但有两个设计值得保留。一个是set -euo pipefail,只要导入或验证过程有异常,脚本立即退出,不会带着错误状态继续往下走;另一个是运行时自动探测回退,如果crictl不可用就默认按docker处理,适配老节点。

进阶一点的做法,是把校验也加进脚本里,在导入前强制检查哈希:

# 在load/import之前先校验压缩包 sha256sum -c "$ARCHIVE.xz.sha256"

这样整个导入链路就成了“校验-解压-导入-验证”四步自动化,不需要人工盯着。最后说一下我的个人习惯:每次导入完成后,我会把crictl images | grep calico的输出存成一个文本文件,和离线包放在同一个目录。这样下次巡检时,不需要挨个上节点查,直接对比文件就知道哪台机器的镜像版本对不上。

Calico离线包这件事,真正难的不是镜像有多大,而是环境差异带来的不确定性。如果你也在做v3.25.0离线交付,希望这篇笔记能帮你少走一步弯路,也希望你的离线包一次就能导入成功。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询