码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 【重识云原生】第六章容器基础6.4.9.3节——Service拓扑感知


    1 拓扑感知

    1.1 使用拓扑键实现拓扑感知的流量路由特性状态 

    Kubernetes v1.21 [deprecated]

    说明:

            此功能特性,尤其是 Alpha 阶段的 topologyKeys API,在 Kubernetes v1.21 版本中已被废弃。Kubernetes v1.21 版本中引入的 拓扑感知的提示, 提供类似的功能。

            服务拓扑(Service Topology)可以让一个服务基于集群的 Node 拓扑进行流量路由。 例如,一个服务可以指定流量是被优先路由到一个和客户端在同一个 Node 或者在同一可用区域的端点。

    1.2 拓扑感知的流量路由

            默认情况下,发往 ClusterIP 或者 NodePort 服务的流量可能会被路由到服务的任一后端的地址。Kubernetes 1.7 允许将“外部”流量路由到接收到流量的节点上的 Pod。对于 ClusterIP 服务,无法完成同节点优先的路由,你也无法配置集群优选路由到同一可用区中的端点。 通过在 Service 上配置 topologyKeys,你可以基于来源节点和目标节点的标签来定义流量路由策略。

            通过对源和目的之间的标签匹配,作为集群操作者的你可以根据节点间彼此“较近”和“较远” 来定义节点集合。你可以基于符合自身需求的任何度量值来定义标签。 例如,在公有云上,你可能更偏向于把流量控制在同一区内,因为区间流量是有费用成本的,而区内流量则没有。 其它常见需求还包括把流量路由到由 DaemonSet 管理的本地 Pod 上,或者把将流量转发到连接在同一机架交换机的节点上,以获得低延时。

    1.3 k8s 亲和性

            service 的就近转发实际就是一种网络的亲和性,倾向于转发到离自己比较近的 endpoint。在此特性之前,已经在调度和存储方面有一些亲和性的设计与实现:

    • 节点亲和性 (Node Affinity): 让 Pod 被调度到符合一些期望条件的 Node 上,比如限制调度到某一可用区,或者要求节点支持 GPU,这算是调度亲和,调度结果取决于节点属性。
    • Pod 亲和性与反亲和性 (Pod Affinity/AntiAffinity): 让一组 Pod 调度到同一拓扑域的节点上,或者打散到不同拓扑域的节点, 这也算是调度亲和,调度结果取决于其它 Pod。
    • 数据卷拓扑感知调度 (Volume Topology-aware Scheduling): 让 Pod 只被调度到符合其绑定的存储所在拓扑域的节点上,这算是调度与存储的亲和,调度结果取决于存储的拓扑域。
    • 本地数据卷 (Local Persistent Volume): 让 Pod 使用本地数据卷,比如高性能 SSD,在某些需要高 IOPS 低时延的场景很有用,它还会保证 Pod 始终被调度到同一节点,数据就不会不丢失,这也算是调度与存储的亲和,调度结果取决于存储所在节点。
    • 数据卷拓扑感知动态创建 (Topology-Aware Volume Dynamic Provisioning):先调度 Pod,再根据 Pod 所在节点的拓扑域来创建存储,这算是存储与调度的亲和,存储的创建取决于调度的结果。

            而 k8s 之前在网络方面还没有亲和性能力,拓扑感知服务路由这个新特性恰好可以补齐这个的空缺,此特性使得 service 可以实现就近转发而不是所有 endpoint 等概率转发。

    1.4 实现原理

            我们知道,service 转发主要是 node 上的 kube-proxy 进程通过 watch apiserver 获取 service 对应的 endpoint,再写入 iptables 或 ipvs 规则来实现的;而对于 headless service,主要是通过 kube-dns 或 coredns 动态解析到不同 endpoint ip 来实现的。实现 service 就近转发的关键点就在于如何将流量转发到跟当前节点在同一拓扑域的 endpoint 上,也就是会进行一次 endpoint 筛选,选出一部分符合当前节点拓扑域的 endpoint 进行转发。

            那么如何判断 endpoint 跟当前节点是否在同一拓扑域里呢?只要能获取到 endpoint 的拓扑信息,用它跟当前节点拓扑对比下就可以知道了。那又如何获取 endpoint 的拓扑信息呢?答案是通过 endpoint 所在节点的 label,我们可以使用 node label 来描述拓扑域。

            通常在节点初始化的时候,controller-manager 就会为节点打上许多 label,比如 kubernetes.io/hostname表示节点的 hostname 来区分节点;另外,在云厂商提供的 k8s 服务,或者使用 cloud-controller-manager 的自建集群,通常还会给节点打上 failure-domain.beta.kubernetes.io/zone  和 failure-domain.beta.kubernetes.io/region 以区分节点所在可用区和所在地域,但自 v1.17 开始将会改名成 topology.kubernetes.io/zone 和 topology.kubernetes.io/region,参见 PR 81431。

            如何根据 endpoint 查到它所在节点的这些 label 呢?答案是通过 Endpoint Slice,该特性在v1.16发布了 alpha,在v1.17进入beta,它相当于 Endpoint API 增强版,通过将 endpoint 做数据分片来解决大规模 endpoint 的性能问题,并且可以携带更多的信息,包括 endpoint 所在节点的拓扑信息,拓扑感知服务路由特性会通过 Endpoint Slice 获取这些拓扑信息实现 endpoint 筛选 (过滤出在同一拓扑域的 endpoint),然后再转换为 iptables 或 ipvs 规则写入节点以实现拓扑感知的路由转发。

            之前每个节点上转发 service 的 iptables/ipvs 规则基本是一样的,但启用了拓扑感知服务路由特性之后,每个节点上的转发规则就可能不一样了,因为不同节点的拓扑信息不一样,导致过滤出的 endpoint 就不一样,也正是因为这样,service 转发变得不再等概率,灵活的就近转发才得以实现。

    1.5 使用服务拓扑

            如果集群启用了 ServiceTopology 特性门控, 就可以在 Service 规约中设定 topologyKeys 字段,从而控制其流量路由。 此字段是 Node 标签的优先顺序字段,将用于在访问这个 Service 时对端点进行排序。 流量会被定向到第一个标签值和源 Node 标签值相匹配的 Node。 如果这个 Service 没有匹配的后端 Node,那么第二个标签会被使用做匹配,以此类推,直到没有标签。

            如果没有匹配到,流量会被拒绝,就如同这个 Service 根本没有后端。 换言之,系统根据可用后端的第一个拓扑键来选择端点。 如果这个字段被配置了而没有后端可以匹配客户端拓扑,那么这个 Service 对那个客户端是没有后端的,链接应该是失败的。 这个字段配置为 "*" 意味着任意拓扑。 这个通配符值如果使用了,那么只有作为配置值列表中的最后一个才有用。

            如果 topologyKeys 没有指定或者为空,就没有启用这个拓扑约束。

            一个集群中,其 Node 的标签被打为其主机名、区域名和地区名。 那么就可以设置Service的topologyKeys的值,像下面的做法一样定向流量了。

    • 只定向到同一个 Node 上的端点,Node 上没有端点存在时就失败: 配置 ["kubernetes.io/hostname"]。
    • 偏向定向到同一个 Node 上的端点,回退同一区域的端点上,然后是同一地区, 其它情况下就失败:配置 ["kubernetes.io/hostname", "topology.kubernetes.io/zone", "topology.kubernetes.io/region"]。 这或许很有用,例如,数据局部性很重要的情况下。
    • 偏向于同一区域,但如果此区域中没有可用的终结点,则回退到任何可用的终结点: 配置 ["topology.kubernetes.io/zone", "*"]。

    1.6 约束条件

    • 服务拓扑和 externalTrafficPolicy=Local 是不兼容的,所以 Service 不能同时使用这两种特性。 但是在同一个集群的不同 Service 上是可以分别使用这两种特性的,只要不在同一个 Service 上就可以。
    • 有效的拓扑键目前只有:kubernetes.io/hostname、topology.kubernetes.io/zone和topology.kubernetes.io/region,但是未来会推广到其它的 Node 标签。
    • 拓扑键必须是有效的标签,并且最多指定16个。
    • 通配符:"*",如果要用,则必须是拓扑键值的最后一个值。

    2 实操

    2.1 配置示例

            以下是使用服务拓扑功能的常见示例。

    2.1.1 仅节点本地端点

            仅路由到节点本地端点的一种服务。如果节点上不存在端点,流量则被丢弃:

    1. apiVersion: v1
    2. kind: Service
    3. metadata:
    4. name: my-service
    5. spec:
    6. selector:
    7. app: my-app
    8. ports:
    9. - protocol: TCP
    10. port: 80
    11. targetPort: 9376
    12. topologyKeys:
    13. - "kubernetes.io/hostname"

    2.1.2 首选节点本地端点

            首选节点本地端点,如果节点本地端点不存在,则回退到集群范围端点的一种服务:

    1. apiVersion: v1
    2. kind: Service
    3. metadata:
    4. name: my-service
    5. spec:
    6. selector:
    7. app: my-app
    8. ports:
    9. - protocol: TCP
    10. port: 80
    11. targetPort: 9376
    12. topologyKeys:
    13. - "kubernetes.io/hostname"
    14. - "*"

    2.1.3 仅地域或区域端点

            首选地域端点而不是区域端点的一种服务。 如果以上两种范围内均不存在端点, 流量则被丢弃。

    1. apiVersion: v1
    2. kind: Service
    3. metadata:
    4. name: my-service
    5. spec:
    6. selector:
    7. app: my-app
    8. ports:
    9. - protocol: TCP
    10. port: 80
    11. targetPort: 9376
    12. topologyKeys:
    13. - "topology.kubernetes.io/zone"
    14. - "topology.kubernetes.io/region"

    2.1.4 优先选择节点本地端点、地域端点,然后是区域端点

            优先选择节点本地端点,地域端点,然后是区域端点,最后才是集群范围端点的 一种服务。

    1. apiVersion: v1
    2. kind: Service
    3. metadata:
    4. name: my-service
    5. spec:
    6. selector:
    7. app: my-app
    8. ports:
    9. - protocol: TCP
    10. port: 80
    11. targetPort: 9376
    12. topologyKeys:
    13. - "kubernetes.io/hostname"
    14. - "topology.kubernetes.io/zone"
    15. - "topology.kubernetes.io/region"
    16. - "*"

    2.2 使用实例

            我们创建两个deploment,一个带topologykey的Service,然后来观察创建的ipvs规则是否是按照我们约定的拓扑结构来创建的。

    2.2.1 创建deployment

    beijing_deploy.yaml

    1. apiVersion: apps/v1
    2. kind: Deployment
    3. metadata:
    4. labels:
    5. app: ud-test
    6. apps.openyurt.io/pool-name: beijing
    7. name: ud-test-beijing-btwbn
    8. namespace: default
    9. spec:
    10. progressDeadlineSeconds: 600
    11. replicas: 1
    12. revisionHistoryLimit: 5
    13. selector:
    14. matchLabels:
    15. app: ud-test
    16. apps.openyurt.io/pool-name: beijing
    17. strategy:
    18. rollingUpdate:
    19. maxSurge: 25%
    20. maxUnavailable: 25%
    21. type: RollingUpdate
    22. template:
    23. metadata:
    24. creationTimestamp: null
    25. labels:
    26. app: ud-test
    27. apps.openyurt.io/pool-name: beijing
    28. spec:
    29. affinity:
    30. nodeAffinity:
    31. requiredDuringSchedulingIgnoredDuringExecution:
    32. nodeSelectorTerms:
    33. - matchExpressions:
    34. - key: apps.openyurt.io/nodepool
    35. operator: In
    36. values:
    37. - beijing
    38. containers:
    39. - image: registry.cn-hangzhou.aliyuncs.com/dice-third-party/nginx:1.14.0
    40. imagePullPolicy: Always
    41. name: nginx
    42. resources: {}
    43. terminationMessagePath: /dev/termination-log
    44. terminationMessagePolicy: File
    45. dnsPolicy: ClusterFirst
    46. restartPolicy: Always
    47. schedulerName: default-scheduler
    48. securityContext: {}
    49. terminationGracePeriodSeconds: 30

    hangzhou_deploy.yaml

    1. apiVersion: apps/v1
    2. kind: Deployment
    3. metadata:
    4. labels:
    5. app: ud-test
    6. apps.openyurt.io/pool-name: hangzhou
    7. name: ud-test-hangzhou-btwbn
    8. namespace: default
    9. spec:
    10. progressDeadlineSeconds: 600
    11. replicas: 1
    12. revisionHistoryLimit: 5
    13. selector:
    14. matchLabels:
    15. app: ud-test
    16. apps.openyurt.io/pool-name: hangzhou
    17. strategy:
    18. rollingUpdate:
    19. maxSurge: 25%
    20. maxUnavailable: 25%
    21. type: RollingUpdate
    22. template:
    23. metadata:
    24. creationTimestamp: null
    25. labels:
    26. app: ud-test
    27. apps.openyurt.io/pool-name: hangzhou
    28. spec:
    29. affinity:
    30. nodeAffinity:
    31. requiredDuringSchedulingIgnoredDuringExecution:
    32. nodeSelectorTerms:
    33. - matchExpressions:
    34. - key: apps.openyurt.io/nodepool
    35. operator: In
    36. values:
    37. - hangzhou
    38. containers:
    39. - image: registry.cn-hangzhou.aliyuncs.com/dice-third-party/nginx:1.14.0
    40. imagePullPolicy: Always
    41. name: nginx
    42. resources: {}
    43. terminationMessagePath: /dev/termination-log
    44. terminationMessagePolicy: File
    45. dnsPolicy: ClusterFirst
    46. restartPolicy: Always
    47. schedulerName: default-scheduler
    48. securityContext: {}
    49. terminationGracePeriodSeconds: 30

    2.2.2 创建service

            增加了topoloKeys的字段,用于给kube-proxy做拓扑感知使用,kube-proxy会根据自身的该标签来过滤endpointslice中的endpoin信息设置自己的负载均衡策略,所以相应的节点上也需要打上http://topology.kubernetes.io/zone的标签。

    1. apiVersion: v1
    2. kind: Service
    3. metadata:
    4. name: ud-test
    5. labels:
    6. app: ud-test
    7. spec:
    8. ports:
    9. - name: ud-test
    10. port: 80
    11. targetPort: 80
    12. selector:
    13. app: ud-test
    14. topologyKeys:
    15. - "topology.kubernetes.io/zone"
    16. # - "*"

    2.2.3 观察效果

            创建完成后,我们们可以看到一个service资源,对应两个pod资源,同时会生成一个EndpointSlice资源。

            然后我们登陆到有http://topology.kubernetes.io/zone节点上的去查看对应的ipvs规则。

            可以看到ipvs规则对应的后端pod的ip只有对应节点的标签过滤出来的endpoint信息。

             如果节点没有打http://topology.kubernetes.io/zone呢,那就不会有任何后端信息。这也不合理,所有k8s给了一个*选项,如果没有匹配到标签,则后端是所有的endpoint集合。

    参考链接

    开启服务拓扑 | Kubernetes

    使用拓扑键实现拓扑感知的流量路由 | Kubernetes

    我参与的k8s v1.17 新特性: 拓扑感知服务路由

    详解K8s资源拓扑感知调度、资源优化策略最佳实践 - 腾讯云开发者社区-腾讯云

    如何获取k8s拓扑_k8s从安装到精通--Service 拓扑介绍_weixin_39525243的博客-CSDN博客

    Kubernetes 资源拓扑感知调度优化

    Kubernetes Service 开启拓扑感知(就近访问)能力_shida_csdn的博客-CSDN博客_k8s 拓扑感知

       《重识云原生系列》专题索引:

    1. 第一章——不谋全局不足以谋一域
    2. 第二章计算第1节——计算虚拟化技术总述
    3. 第二章计算第2节——主流虚拟化技术之VMare ESXi
    4. 第二章计算第3节——主流虚拟化技术之Xen
    5. 第二章计算第4节——主流虚拟化技术之KVM
    6. 第二章计算第5节——商用云主机方案
    7. 第二章计算第6节——裸金属方案
    8. 第三章云存储第1节——分布式云存储总述
    9. 第三章云存储第2节——SPDK方案综述
    10. 第三章云存储第3节——Ceph统一存储方案
    11. 第三章云存储第4节——OpenStack Swift 对象存储方案
    12. 第三章云存储第5节——商用分布式云存储方案
    13. 第四章云网络第一节——云网络技术发展简述
    14. 第四章云网络4.2节——相关基础知识准备
    15. 第四章云网络4.3节——重要网络协议
    16. 第四章云网络4.3.1节——路由技术简述
    17. 第四章云网络4.3.2节——VLAN技术
    18. 第四章云网络4.3.3节——RIP协议
    19. 第四章云网络4.3.4.1-2节——OSPF协议综述
    20. 第四章云网络4.3.4.3节——OSPF协议工作原理
    21. 第四章云网络4.3.4.4节——[转载]OSPF域内路由
    22. 第四章云网络4.3.4.5节——[转载]OSPF外部路由
    23. 第四章云网络4.3.4.6节——[转载]OSPF特殊区域之Stub和Totally Stub区域详解及配置
    24. 第四章云网络4.3.4.7节——OSPF特殊区域之NSSA和Totally NSSA详解及配置
    25. 第四章云网络4.3.5节——EIGRP协议
    26. 第四章云网络4.3.6节——IS-IS协议
    27. 第四章云网络4.3.7节——BGP协议
    28. 第四章云网络4.3.7.2节——BGP协议概述
    29. 第四章云网络4.3.7.3节——BGP协议实现原理
    30. 第四章云网络4.3.7.4节——高级特性
    31. 第四章云网络4.3.7.5节——实操
    32. 第四章云网络4.3.7.6节——MP-BGP协议
    33. 第四章云网络4.3.8节——策略路由
    34. 第四章云网络4.3.9节——Graceful Restart(平滑重启)技术
    35. 第四章云网络4.3.10节——VXLAN技术
    36. 第四章云网络4.3.10.2节——VXLAN Overlay网络方案设计
    37. 第四章云网络4.3.10.3节——VXLAN隧道机制
    38. 第四章云网络4.3.10.4节——VXLAN报文转发过程
    39. 第四章云网络4.3.10.5节——VXlan组网架构
    40. 第四章云网络4.3.10.6节——VXLAN应用部署方案
    41. 第四章云网络4.4节——Spine-Leaf网络架构
    42. 第四章云网络4.5节——大二层网络
    43. 第四章云网络4.6节——Underlay 和 Overlay概念
    44. 第四章云网络4.7.1节——网络虚拟化与卸载加速技术的演进简述
    45. 第四章云网络4.7.2节——virtio网络半虚拟化简介
    46. 第四章云网络4.7.3节——Vhost-net方案
    47. 第四章云网络4.7.4节vhost-user方案——virtio的DPDK卸载方案
    48. 第四章云网络4.7.5节vDPA方案——virtio的半硬件虚拟化实现
    49. 第四章云网络4.7.6节——virtio-blk存储虚拟化方案
    50. 第四章云网络4.7.8节——SR-IOV方案
    51. 第四章云网络4.7.9节——NFV
    52. 第四章云网络4.8.1节——SDN总述
    53. 第四章云网络4.8.2.1节——OpenFlow概述
    54. 第四章云网络4.8.2.2节——OpenFlow协议详解
    55. 第四章云网络4.8.2.3节——OpenFlow运行机制
    56. 第四章云网络4.8.3.1节——Open vSwitch简介
    57. 第四章云网络4.8.3.2节——Open vSwitch工作原理详解
    58. 第四章云网络4.8.4节——OpenStack与SDN的集成
    59. 第四章云网络4.8.5节——OpenDayLight
    60. 第四章云网络4.8.6节——Dragonflow
    61.  第四章云网络4.9.1节——网络卸载加速技术综述

    62. 第四章云网络4.9.2节——传统网络卸载技术

    63. 第四章云网络4.9.3.1节——DPDK技术综述

    64. 第四章云网络4.9.3.2节——DPDK原理详解

    65. 第四章云网络4.9.4.1节——智能网卡SmartNIC方案综述

    66. 第四章云网络4.9.4.2节——智能网卡实现

    67. 第六章容器6.1.1节——容器综述

    68. 第六章容器6.1.2节——容器安装部署

    69. 第六章容器6.1.3节——Docker常用命令

    70. 第六章容器6.1.4节——Docker核心技术LXC

    71. 第六章容器6.1.5节——Docker核心技术Namespace

    72. 第六章容器6.1.6节—— Docker核心技术Chroot

    73. 第六章容器6.1.7.1节——Docker核心技术cgroups综述

    74. 第六章容器6.1.7.2节——cgroups原理剖析

    75. 第六章容器6.1.7.3节——cgroups数据结构剖析

    76. 第六章容器6.1.7.4节——cgroups使用

    77. 第六章容器6.1.8节——Docker核心技术UnionFS

    78. 第六章容器6.1.9节——Docker镜像技术剖析

    79. 第六章容器6.1.10节——DockerFile解析

    80. 第六章容器6.1.11节——docker-compose容器编排

    81. 第六章容器6.1.12节——Docker网络模型设计

    82. 第六章容器6.2.1节——Kubernetes概述

    83. 第六章容器6.2.2节——K8S架构剖析

    84. 第六章容器6.3.1节——K8S核心组件总述

    85. 第六章容器6.3.2节——API Server组件

    86. 第六章容器6.3.3节——Kube-Scheduler使用篇

    87. 第六章容器6.3.4节——etcd组件

    88. 第六章容器6.3.5节——Controller Manager概述

    89. 第六章容器6.3.6节——kubelet组件

    90. 第六章容器6.3.7节——命令行工具kubectl

    91. 第六章容器6.3.8节——kube-proxy

    92.  第六章容器6.4.1节——K8S资源对象总览

    93. 第六章容器6.4.2.1节——pod详解

    94. 第六章容器6.4.2.2节——Pod使用(上)

    95. 第六章容器6.4.2.3节——Pod使用(下)

    96. 第六章容器6.4.3节——ReplicationController

    97. 第六章容器6.4.4节——ReplicaSet组件

    98. 第六章容器基础6.4.5.1节——Deployment概述

    99. 第六章容器基础6.4.5.2节——Deployment配置详细说明

    100. 第六章容器基础6.4.5.3节——Deployment实现原理解析

    101. 第六章容器基础6.4.6节——Daemonset

    102. 第六章容器基础6.4.7节——Job

    103. 第六章容器基础6.4.8节——CronJob

    104. 第六章容器基础6.4.9.1节——Service综述

    105. 第六章容器基础6.4.9.2节——使用 Service 连接到应用

    106. 第六章容器基础6.4.9.3节——Service拓扑感知

    107. 第六章容器基础6.4.10.1节——StatefulSet概述

    108. 第六章容器基础6.4.10.2节——StatefulSet常规操作实操

    109. 第六章容器基础6.4.10.3节——StatefulSet实操案例-部署WordPress 和 MySQL

    110. 第六章容器基础6.4.10.4节——StatefulSet实操案例-使用 StatefulSet 部署Cassandra

    111. 第六章容器基础6.4.10.5节——Statefulset原理剖析

    112. 第六章容器基础6.4.11.1节——Ingress综述

  • 相关阅读:
    Android 内存优化&内存泄漏处理
    如何通过在线培训考试系统进行远程教育
    小啊呜产品读书笔记001:《邱岳的产品手记-04》第07+08讲 关于需求变更
    每日一道面试题之什么是上下文切换?
    springboot在filter中设置跨域
    计算机算法分析与设计(23)---二分搜索算法(C++)
    解决telnet不是内部或外部以及验证某个端口是否开放
    QT设计模式:适配器模式
    【故障补牢】贪吃的 Bing 爬虫,限量供应的应对措施
    23种设计模式(十九)职责链模式(阁瑞钛伦特软件-九耶实训)
  • 原文地址:https://blog.csdn.net/junbaozi/article/details/127780300
  • 最新文章
  • 攻防演习之三天拿下官网站群
    数据安全治理学习——前期安全规划和安全管理体系建设
    企业安全 | 企业内一次钓鱼演练准备过程
    内网渗透测试 | Kerberos协议及其部分攻击手法
    0day的产生 | 不懂代码的"代码审计"
    安装scrcpy-client模块av模块异常,环境问题解决方案
    leetcode hot100【LeetCode 279. 完全平方数】java实现
    OpenWrt下安装Mosquitto
    AnatoMask论文汇总
    【AI日记】24.11.01 LangChain、openai api和github copilot
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
正则表达式工具 cron表达式工具 密码生成工具

京公网安备 11010502049817号