• kube-prometheus 监控kubernetes集群


    一、概述

    1、Prometheus简介

           Prometheus是一个开源的服务监控系统和时序数据库,其提供了通用的数据模型和快捷数据采集、存储和查询接口。它的核心组件Prometheus服务器定期从静态配置的监控目标或者基于服务发现自动配置的目标中进行拉取数据,新拉取到啊的 数据大于配置的内存缓存区时,数据就会持久化到存储设备当中。Prometheus组件架构图如下:

            如上图,每个被监控的主机都可以通过专用的exporter程序提供输出监控数据的接口,并等待Prometheus服务器周期性的进行数据抓取。如果存在告警规则,则抓取到数据之后会根据规则进行计算,满足告警条件则会生成告警,并发送到Alertmanager完成告警的汇总和分发。当被监控的目标有主动推送数据的需求时,可以以Pushgateway组件进行接收并临时存储数据,然后等待Prometheus服务器完成数据的采集。

    任何被监控的目标都需要事先纳入到监控系统中才能进行时序数据采集、存储、告警和展示,监控目标可以通过配置信息以静态形式指定,也可以让Prometheus通过服务发现的机制进行动态管理。下面是组件的一些解析:

    • 监控代理程序:如node_exporter:收集主机的指标数据,如平均负载、CPU、内存、磁盘、网络等等多个维度的指标数据。
    • kubelet(cAdvisor):收集容器指标数据,也是K8S的核心指标收集,每个容器的相关指标数据包括:CPU使用率、限额、文件系统读写限额、内存使用率和限额、网络报文发送、接收、丢弃速率等等。
    • API Server:收集API Server的性能指标数据,包括控制队列的性能、请求速率和延迟时长等等
    • etcd:收集etcd存储集群的相关指标数据
    • kube-state-metrics:该组件可以派生出k8s相关的多个指标数据,主要是资源类型相关的计数器和元数据信息,包括制定类型的对象总数、资源限额、容器状态以及Pod资源标签系列等。

    Prometheus 能够 直接 把 Kubernetes API Server 作为 服务 发现 系统 使用 进而 动态 发现 和 监控 集群 中的 所有 可被 监控 的 对象。 这里 需要 特别 说明 的 是, Pod 资源 需要 添加 下列 注解 信息 才 能被 Prometheus 系统 自动 发现 并 抓取 其 内建 的 指标 数据。

    • 1) prometheus. io/ scrape: 用于 标识 是否 需要 被 采集 指标 数据, 布尔 型 值, true 或 false。
    • 2) prometheus. io/ path: 抓取 指标 数据 时 使用 的 URL 路径, 一般 为/ metrics。
    • 3) prometheus. io/ port: 抓取 指标 数据 时 使 用的 套 接 字 端口, 如 8080。

    另外, 仅 期望 Prometheus 为 后端 生成 自定义 指标 时 仅 部署 Prometheus 服务器 即可, 它 甚至 也不 需要 数据 持久 功能。 但 若要 配置 完整 功能 的 监控 系统, 管理员 还需 要在 每个 主机 上 部署 node_ exporter、 按 需 部署 其他 特有 类型 的 exporter 以及 Alertmanager。

    2、Prometheus-Operator的架构

    上图是Prometheus-Operator官方提供的架构图,从下向上看,Operator可以部署并且管理Prometheus Server,并且Operator可以观察Prometheus,那么这个观察是什么意思呢,上图中Service1 - Service5其实就是kubernetes的service,kubernetes的资源分为Service、Deployment、ServiceMonitor、ConfigMap等等,所以上图中的Service和ServiceMonitor都是kubernetes资源,一个ServiceMonitor可以通过labelSelector的方式去匹配一类Service(一般来说,一个ServiceMonitor对应一个Service),Prometheus也可以通过labelSelector去匹配多个ServiceMonitor。

    • Operator : Operator是整个系统的主要控制器,会以Deployment的方式运行于Kubernetes集群上,并根据自定义资源(Custom Resources Definition)CRD 来负责管理与部署Prometheus,Operator会通过监听这些CRD的变化来做出相对应的处理。

    • Prometheus : Operator会观察集群内的Prometheus CRD(Prometheus 也是一种CRD)来创建一个合适的statefulset在monitoring(.metadata.namespace指定)命名空间,并且挂载了一个名为prometheus-k8s的Secret为Volume到/etc/prometheus/config目录,Secret的data包含了以下内容

      • configmaps.json指定了rule-files在configmap的名字
      • prometheus.yaml为主配置文件
    • ServiceMonitor : ServiceMonitor就是一种kubernetes自定义资源(CRD),Operator会通过监听ServiceMonitor的变化来动态生成Prometheus的配置文件中的Scrape targets,并让这些配置实时生效,operator通过将生成的job更新到上面的prometheus-k8s这个Secret的Data的prometheus.yaml字段里,然后prometheus这个pod里的sidecar容器prometheus-config-reloader当检测到挂载路径的文件发生改变后自动去执行HTTP Post请求到/api/-reload-路径去reload配置。该自定义资源(CRD)通过labels选取对应的Service,并让prometheus server通过选取的Service拉取对应的监控信息(metric)

    • Service :Service其实就是指kubernetes的service资源,这里特指Prometheus exporter的service,比如部署在kubernetes上的mysql-exporter的service

           想象一下,我们以传统的方式去监控一个mysql服务,首先需要安装mysql-exporter,获取mysql metrics,并且暴露一个端口,等待prometheus服务来拉取监控信息,然后去Prometheus Server的prometheus.yaml文件中在scarpe_config中添加mysql-exporter的job,配置mysql-exporter的地址和端口等信息,再然后,需要重启Prometheus服务,就完成添加一个mysql监控的任务

           现在我们以Prometheus-Operator的方式来部署Prometheus,当我们需要添加一个mysql监控我们会怎么做,首先第一步和传统方式一样,部署一个mysql-exporter来获取mysql监控项,然后编写一个ServiceMonitor通过labelSelector选择刚才部署的mysql-exporter,由于Operator在部署Prometheus的时候默认指定了Prometheus选择label为:prometheus: kube-prometheus的ServiceMonitor,所以只需要在ServiceMonitor上打上prometheus: kube-prometheus标签就可以被Prometheus选择了,完成以上两步就完成了对mysql的监控,不需要改Prometheus配置文件,也不需要重启Prometheus服务,是不是很方便,Operator观察到ServiceMonitor发生变化,会动态生成Prometheus配置文件,并保证配置文件实时生效

    2、kube-prometheus 介绍

           目前我使用的是  kube-prometheus 版本为 kube-prometheus-release-0.3

            首先Prometheus整体监控结构略微复杂,一个个部署并不简单。另外监控Kubernetes就需要访问内部数据,必定需要进行认证、鉴权、准入控制,那么这一整套下来将变得难上加难,而且还需要花费一定的时间,如果你没有特别高的要求,我还是建议选用开源比较好的一些方案。

    本篇主要针对Kubernetes部署Prometheus相关配置介绍,本人采用的是github开源的部署方案:/kube-prometheus关于这个kube-prometheus目前应该是开源最好的方案了,该存储库收集Kubernetes清单,Grafana仪表板和Prometheus规则,以及文档和脚本,

    以使用Prometheus Operator 通过Prometheus提供易于操作的端到端Kubernetes集群监视。以容器的方式部署到k8s集群,而且还可以自定义配置,非常的方便。

           kube-prometheus相关部署文件在manifests目录中,共65个yaml,其中setup文件夹中包含所有自定义资源配置CustomResourceDefinition(一般不用修改,也不要轻易修改),所以部署时必须先执行这个文件夹。其中包括告警(Alertmanager)、监控(Prometheus)、监控项(PrometheusRule)这三类资源定义,所以如果你想直接在k8s中修改对应控制器配置是没有用的(比如kubectl edit sts prometheus-k8s -n monitoring) 。这里yaml文件看着很多,只要我们梳理一下就会很容易理解了,首先分为7个组件prometheus-operator、prometheus-adapter、prometheus、alertmanager、grafana、kube-state-metrics、node-exporter,然后每个组件都会定义控制器、配置文件、集群权限、访问配置、监控配置, 但是我们一般只需要进行自定义告警配置和监控项,这样一筛选发现只需要修改几个文件即可(其中红色后面重点说明,紫色可根据项目情况调整资源配置)。

    [root@k8s-master yaml]# git clone https://github.com/prometheus-operator/kube-prometheus.git --branch release-0.3 --single-branch
    [root@localhost manifests]$ tree
    .
    ├── alertmanager-alertmanager.yaml      
    ├── alertmanager-secret.yaml
    ├── alertmanager-serviceAccount.yaml
    ├── alertmanager-serviceMonitor.yaml
    ├── alertmanager-service.yaml
    ├── grafana-dashboardDatasources.yaml
    ├── grafana-dashboardDefinitions.yaml
    ├── grafana-dashboardSources.yaml
    ├── grafana-deployment.yaml
    ├── grafana-serviceAccount.yaml
    ├── grafana-serviceMonitor.yaml
    ├── grafana-service.yaml
    ├── kube-state-metrics-clusterRoleBinding.yaml
    ├── kube-state-metrics-clusterRole.yaml
    ├── kube-state-metrics-deployment.yaml
    ├── kube-state-metrics-roleBinding.yaml
    ├── kube-state-metrics-role.yaml
    ├── kube-state-metrics-serviceAccount.yaml
    ├── kube-state-metrics-serviceMonitor.yaml
    ├── kube-state-metrics-service.yaml
    ├── node-exporter-clusterRoleBinding.yaml
    ├── node-exporter-clusterRole.yaml
    ├── node-exporter-daemonset.yaml
    ├── node-exporter-serviceAccount.yaml
    ├── node-exporter-serviceMonitor.yaml
    ├── node-exporter-service.yaml
    ├── prometheus-adapter-apiService.yaml
    ├── prometheus-adapter-clusterRoleAggregatedMetricsReader.yaml
    ├── prometheus-adapter-clusterRoleBindingDelegator.yaml
    ├── prometheus-adapter-clusterRoleBinding.yaml
    ├── prometheus-adapter-clusterRoleServerResources.yaml
    ├── prometheus-adapter-clusterRole.yaml
    ├── prometheus-adapter-configMap.yaml
    ├── prometheus-adapter-deployment.yaml
    ├── prometheus-adapter-roleBindingAuthReader.yaml
    ├── prometheus-adapter-serviceAccount.yaml
    ├── prometheus-adapter-service.yaml
    ├── prometheus-clusterRoleBinding.yaml
    ├── prometheus-clusterRole.yaml 
    ├── prometheus-operator-serviceMonitor.yaml
    ├── prometheus-prometheus.yaml
    ├── prometheus-roleBindingConfig.yaml
    ├── prometheus-roleBindingSpecificNamespaces.yaml
    ├── prometheus-roleConfig.yaml
    ├── prometheus-roleSpecificNamespaces.yaml
    ├── prometheus-rules.yaml
    ├── prometheus-serviceAccount.yaml
    ├── prometheus-serviceMonitorApiserver.yaml
    ├── prometheus-serviceMonitorCoreDNS.yaml
    ├── prometheus-serviceMonitorKubeControllerManager.yaml
    ├── prometheus-serviceMonitorKubelet.yaml
    ├── prometheus-serviceMonitorKubeScheduler.yaml
    ├── prometheus-serviceMonitor.yaml
    ├── prometheus-service.yaml
    └── setup
        ├── 0namespace-namespace.yaml
        ├── prometheus-operator-0alertmanagerCustomResourceDefinition.yaml
        ├── prometheus-operator-0podmonitorCustomResourceDefinition.yaml
        ├── prometheus-operator-0prometheusCustomResourceDefinition.yaml
        ├── prometheus-operator-0prometheusruleCustomResourceDefinition.yaml
        ├── prometheus-operator-0servicemonitorCustomResourceDefinition.yaml
        ├── prometheus-operator-clusterRoleBinding.yaml
        ├── prometheus-operator-clusterRole.yaml
        ├── prometheus-operator-deployment.yaml
        ├── prometheus-operator-serviceAccount.yaml
        └── prometheus-operator-service.yaml
    
    1 directory, 65 files
    • alertmanager模块主要针对告警功能
    • grafana 仪表盘
    • kube-state-metrics

              kube-state-metrics是一个简单的服务,它监听Kubernetes API服务器并生成关于对象状态的度量。(请参阅下面度量标准一节中的示例。)它不关注单个Kubernetes组件的运行状况,而是关注其中各种对象(如部署、节点和pod)的运行状况。
            kube-state-metrics是关于不用修改就从Kubernetes API对象生成度量的。这确保了kube-state-metrics提供的特性与Kubernetes API对象本身具有相同级别的稳定性。反过来,这意味着在某些情况下kube-state-metrics可能不会显示与kubectl完全相同的值,因为kubectl应用某些启发性来显示可理解的消息。kube-state-metrics从Kubernetes API中公开未经修改的原始数据,这样用户就可以获得他们需要的所有数据,并在他们认为合适的时候执行试探。
    度量在HTTP端点上导出/度量在侦听端口(默认8080)上导出。它们被用作明文。它们被设计为要么由普罗米修斯自己使用,要么由与抓取普罗米修斯客户端端点兼容的scraper使用。您还可以在浏览器中打开/metrics来查看原始指标。注意,在/metrics端点上公开的度量反映了Kubernetes集群的当前状态。当Kubernetes对象被删除时,它们在/metrics端点上不再可见。

    • node-exporter 监控宿主机的性能
    • prometheus-adapter 实现 custom metrics API,resource metrics API 两种自定义API,   可参考: https://juejin.cn/post/6844903967218991117

    二、部署kube-prometheus

    如果不需要持久化和添加额外监控可以直接运行以下命令执行创建监控

    [root@localhost package]#  cd manifests
    [root@localhost manifests ]#  kubectl apply -f setup/
    [root@localhost manifests ]#  kubectl apply -f .

    以下是我新加的几个yaml文件,主要做了持久化和添加额外监控。

    [root@localhost manifests]$ tree
    .
    ├── alertmanager-alertmanager.yaml      
    ├── alertmanager-secret.yaml
    ├── alertmanager-serviceAccount.yaml
    ├── alertmanager-serviceMonitor.yaml
    ├── alertmanager-service.yaml
    ├── grafana-dashboardDatasources.yaml
    ├── grafana-dashboardDefinitions.yaml
    ├── grafana-dashboardSources.yaml
    ├── grafana-deployment.yaml
    ├── grafana-pvc.yaml           ### 新添加,主要作为 grafan 持久化存储
    ├── grafana-serviceAccount.yaml
    ├── grafana-serviceMonitor.yaml
    ├── grafana-service.yaml
    ├── kube-state-metrics-clusterRoleBinding.yaml
    ├── kube-state-metrics-clusterRole.yaml
    ├── kube-state-metrics-deployment.yaml
    ├── kube-state-metrics-roleBinding.yaml
    ├── kube-state-metrics-role.yaml
    ├── kube-state-metrics-serviceAccount.yaml
    ├── kube-state-metrics-serviceMonitor.yaml
    ├── kube-state-metrics-service.yaml
    ├── node-exporter-clusterRoleBinding.yaml
    ├── node-exporter-clusterRole.yaml
    ├── node-exporter-daemonset.yaml
    ├── node-exporter-serviceAccount.yaml
    ├── node-exporter-serviceMonitor.yaml
    ├── node-exporter-service.yaml
    ├── prometheus-adapter-apiService.yaml
    ├── prometheus-adapter-clusterRoleAggregatedMetricsReader.yaml
    ├── prometheus-adapter-clusterRoleBindingDelegator.yaml
    ├── prometheus-adapter-clusterRoleBinding.yaml
    ├── prometheus-adapter-clusterRoleServerResources.yaml
    ├── prometheus-adapter-clusterRole.yaml
    ├── prometheus-adapter-configMap.yaml
    ├── prometheus-adapter-deployment.yaml
    ├── prometheus-adapter-roleBindingAuthReader.yaml
    ├── prometheus-adapter-serviceAccount.yaml
    ├── prometheus-adapter-service.yaml
    ├── prometheus-additional.yaml      ## 新添加,主要添加 k8s集群外的监控,如其他主机的node-exporter、mysqld-exporter等
    ├── prometheus-clusterRoleBinding.yaml
    ├── prometheus-clusterRole.yaml 
    ├── prometheus-kubeControllerManagerService.yaml        ###新添加,   针对ControllerManagerService 的监控
    ├── prometheus-kubeSchedulerService.yaml    ### 新添加,  主要针对 kubeScheduler 的监控
    ├── prometheus-operator-serviceMonitor.yaml
    ├── prometheus-prometheus.yaml
    ├── prometheus-roleBindingConfig.yaml
    ├── prometheus-roleBindingSpecificNamespaces.yaml
    ├── prometheus-roleConfig.yaml
    ├── prometheus-roleSpecificNamespaces.yaml
    ├── prometheus-rules.yaml
    ├── prometheus-serviceAccount.yaml
    ├── prometheus-serviceMonitorApiserver.yaml
    ├── prometheus-serviceMonitorCoreDNS.yaml
    ├── prometheus-serviceMonitorKubeControllerManager.yaml
    ├── prometheus-serviceMonitorKubelet.yaml
    ├── prometheus-serviceMonitorKubeScheduler.yaml
    ├── prometheus-serviceMonitor.yaml
    ├── prometheus-service.yaml
    └── setup
        ├── 0namespace-namespace.yaml
        ├── prometheus-operator-0alertmanagerCustomResourceDefinition.yaml
        ├── prometheus-operator-0podmonitorCustomResourceDefinition.yaml
        ├── prometheus-operator-0prometheusCustomResourceDefinition.yaml
        ├── prometheus-operator-0prometheusruleCustomResourceDefinition.yaml
        ├── prometheus-operator-0servicemonitorCustomResourceDefinition.yaml
        ├── prometheus-operator-clusterRoleBinding.yaml
        ├── prometheus-operator-clusterRole.yaml
        ├── prometheus-operator-deployment.yaml
        ├── prometheus-operator-serviceAccount.yaml
        └── prometheus-operator-service.yaml
    
    1 directory, 69 files

    三、修改 prometheus 配置

    1、添加k8s外部监控

    一个项目开始可能很难实现全部容器化,比如数据库、CDH集群。但是我们依然需要监控他们,如果分成两套prometheus不利于管理,所以我们统一添加这些监控到kube-prometheus中。

    那么接下来我们新建  prometheus-additional.yaml  文件,添加额外监控组件配置scrape_configs。

     

    - job_name: 'node-exporter-others'
      static_configs:
        - targets:
          - *.*.*.149:31190
          - *.*.*.150:31190
          - *.*.*.122:31190
    
    - job_name: 'mysql-exporter'
      static_configs:
        - targets:
          - *.*.*.104:9592
          - *.*.*.125:9592
          - *.*.*.128:9592
    
    - job_name: 'nacos-exporter'
      metrics_path: '/nacos/actuator/prometheus'
      static_configs:
        - targets:
          - *.*.*.113:8848
          - *.*.*.114:8848
          - *.*.*.118:8848
    
    - job_name: 'elasticsearch-exporter'
      static_configs:
      - targets:
        - *.*.*.110:9597
        - *.*.*.107:9597
        - *.*.*.117:9597
    
    - job_name: 'zookeeper-exporter'
      static_configs:
      - targets:
        - *.*.*.115:9595
        - *.*.*.121:9595
        - *.*.*.120:9595
    
    - job_name: 'nginx-exporter'
      static_configs:
      - targets:
        - *.*.*.149:9593
        - *.*.*.150:9593
        - *.*.*.122:9593
    
    - job_name: 'redis-exporter'
      static_configs:
      - targets:
        - *.*.*.109:9594
    
    - job_name: 'redis-exporter-targets'
      static_configs:
        - targets:
          - redis://*.*.*.146:7090
          - redis://*.*.*.144:7090
          - redis://*.*.*.133:7091
      metrics_path: /scrape
      relabel_configs:
        - source_labels: [__address__]
          target_label: __param_target
        - source_labels: [__param_target]
          target_label: instance
        - target_label: __address__
          replacement: *.*.*.109:9594
    prometheus-additional.yaml

    然后我们需要将这些监控配置以secret资源类型存储到k8s集群中。

    kubectl create secret generic additional-scrape-configs --from-file=prometheus-additional.yaml -n monitoring

    2、修改 prometheus 文件

    vim prometheus-prometheus.yaml

    1)replicas:根据项目情况调整副本数

    2)retention:修改Prometheus数据保留期限,默认值为“24h”,并且必须与正则表达式“ [0-9] +(ms | s | m | h | d | w | y)”匹配。

    3)additionalScrapeConfigs:增加额外监控项配置,具体配置查看第五部分“添加k8s外部监控”。 

    4)添加 持久化存储

    apiVersion: monitoring.coreos.com/v1
    kind: Prometheus
    metadata:
      labels:
        prometheus: k8s
      name: k8s
      namespace: monitoring
    spec:
      imagePullSecrets:
        - name: harborsecret
      alerting:
        alertmanagers:
        - name: alertmanager-main
          namespace: monitoring
          port: web
      baseImage: 10.2.57.16:5000/kubernetes/prometheus
      nodeSelector:
        kubernetes.io/os: linux
      podMonitorNamespaceSelector: {}
      podMonitorSelector: {}
      additionalScrapeConfigs:
        name: additional-scrape-configs
        key: prometheus-additional.yaml
      retention: 15d
      replicas: 2
      resources:
        requests:
          memory: 400Mi
      ruleSelector:
        matchLabels:
          prometheus: k8s
          role: alert-rules
      securityContext:
        fsGroup: 2000
        runAsNonRoot: true
        runAsUser: 1000
      serviceAccountName: prometheus-k8s
      serviceMonitorNamespaceSelector: {}
      serviceMonitorSelector: {}
      version: v2.11.0
      storage:
        volumeClaimTemplate:
          spec:
            storageClassName: monitoring-use
            resources:
              requests:
                storage: 50Gi

    Scheduler和Controller配置

    展开Status菜单,查看targets,可以看到只有图中两个监控任务没有对应的目标,这和serviceMonitor资源对象有关。

     查看yaml文件prometheus-serviceMonitorKubeScheduler.yaml,selector匹配的是service的标签,但是kube-system namespace中并没有k8s-app=kube-scheduler的service

    新建prometheus-kubeSchedulerService.yaml

    apiVersion: v1
    kind: Service
    metadata:
      namespace: kube-system
      name: kube-scheduler
      labels:
        k8s-app: kube-scheduler
    spec:
      selector:
        component: kube-scheduler
      ports:
      - name: http-metrics
        port: 10251
        targetPort: 10251
        protocol: TCP

    同理新建prometheus-kubeControllerManagerService.yaml

    apiVersion: v1
    kind: Service
    metadata:
      namespace: kube-system
      name: kube-controller-manager
      labels:
        k8s-app: kube-controller-manager
    spec:
      selector:
        component: kube-controller-manager
      ports:
      - name: http-metrics
        port: 10252
        targetPort: 10252
        protocol: TCP

    五、Grafana 配置

    默认Grafana不做数据持久化,那么服务重启以后配置的Dashboard、账号密码、监控数据等信息将会丢失,所以做数据持久化也是很有必要的。

    原始的数据是以 emptyDir 形式存放在pod里面,生命周期与pod相同,出现问题时,容器重启,监控相关的数据就全部消失了。

    这里我们通过 storageclass 来做数据持久化,具体配置可以参考:Kubernetes实战总结 - 动态存储管理StorageClass

    对于Grafana来说,我们需要在grafana-deployment.yaml中增加PVC配置,并且更改volumes.grafana-storage。

    新建  grafana-pvc.yaml 文件

    ---
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: grafana
      namespace: monitoring
      annotations:
        volume.beta.kubernetes.io/storage-class: "monitoring-use"
    spec:
      storageClassName: monitoring-use
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 20Gi

    修改 grafana-deployment.yaml

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        app: grafana
      name: grafana
      namespace: monitoring
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: grafana
      template:
        metadata:
          labels:
            app: grafana
        spec:
          imagePullSecrets:
            - name: harborsecret
          containers:
          - image: 10.2.57.16:5000/kubernetes/grafana:6.4.3
            name: grafana
            ports:
            - containerPort: 3000
              name: http
            readinessProbe:
              httpGet:
                path: /api/health
                port: http
            resources:
              limits:
                cpu: 200m
                memory: 200Mi
              requests:
                cpu: 100m
                memory: 100Mi
            volumeMounts:
            - mountPath: /var/lib/grafana
              name: grafana-storage
              readOnly: false
          volumes:
          - name: grafana-storage
            persistentVolumeClaim:
              claimName: grafana

  • 相关阅读:
    中间代码生成器-5-编译原理
    un-动物:老鼠
    un-动物:狗
    un-动物:猫
    un-动物:鹅
    un-动物:鸭子
    un-动物:鸡
    un-常见动物-动物:马
    un-常见动物-动物:骡
    un-常见动物-动物:牛
  • 原文地址:https://www.cnblogs.com/deny/p/14328900.html
Copyright © 2020-2023  润新知