[{"content":"Pod 日志收集 应用程序和系统日志可以帮助我们了解集群内部的运行情况，日志对于我们调试问题和监视集群情况也是非常有用的。而且大部分的应用都会有日志记录，对于传统的应用大部分都会写入到本地的日志文件之中。对于容器化应用程序来说则更简单，只需要将日志信息写入到 stdout 和 stderr 即可，容器默认情况下就会把这些日志输出到宿主机上的一个 JSON 文件之中，同样我们也可以通过 docker logs 或者 kubectl logs 来查看到对应的日志信息。\n但是，通常来说容器引擎或运行时提供的功能不足以记录完整的日志信息，比如，如果容器崩溃了、Pod 被驱逐了或者节点挂掉了，我们仍然也希望访问应用程序的日志。所以，日志应该独立于节点、Pod 或容器的生命周期，这种设计方式被称为 cluster-level-logging，即完全独立于 Kubernetes 系统，需要自己提供单独的日志后端存储、分析和查询工具。\nKubernetes 中大多数的 Pod 日志被输出到控制台，在宿主机的文件系统每个Pod会创建一个存放日志的文件夹/var/log/pods/这里会存放所有这个节点运行的Pod的日志，但是这个文件夹下一般都是软连接，由于Kubernetes 底层的 CRI 容器运行时可以使用很多所以日志本身并不存放在这个文件夹，以下为容器运行时真正存放日志目录：\ncontainer log: /var/log/containers/*.log Pod log：/var/log/pods 集群级别日志架构 使用在每个节点上运行的节点级日志记录代理。 在应用程序的 Pod 中，包含专门记录日志的边车（Sidecar）容器。 将日志直接从应用程序中推送到日志记录后端。 使用节点级日志代理 可以通过在每个节点上使用 节点级的日志记录代理 来实现集群级日志记录。 日志记录代理是一种用于暴露日志或将日志推送到后端的专用工具。 通常，日志记录代理程序是一个容器，它可以访问包含该节点上所有应用程序容器的日志文件的目录。\n由于日志记录代理必须在每个节点上运行，推荐以 DaemonSet 的形式运行该代理。\n节点级日志在每个节点上仅创建一个代理，不需要对节点上的应用做修改。\n容器向标准输出和标准错误输出写出数据，但在格式上并不统一。 节点级代理收集这些日志并将其进行转发以完成汇总。\n使用边车容器运行日志代理 sidecar 从应用中直接暴露日志目录 EFK Yum 仓库\n1 2 3 4 5 6 7 8 9 10 11 rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch vim /etc/yum.repos.d/elasic.repo [elasticsearch] name=Elasticsearch repository for 7.x packages baseurl=https://artifacts.elastic.co/packages/7.x/yum gpgcheck=1 gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch enabled=1 autorefresh=1 type=rpm-md Elasticsearch Elasticsearch 是一个开源的分布式搜索和分析引擎，建立在 Apache Lucene 库之上。它提供了一个高性能、可伸缩和全文搜索能力强大的分布式系统，适用于处理大规模数据集的搜索、分析和近实时数据处理。\n1 2 wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.17.16-x86_64.rpm rpm --install elasticsearch-7.17.16-x86_64.rpm 检查集群状态\n1 2 3 4 5 6 7 8 9 10 # 列出节点健康状态 curl -XGET 127.0.0.1:9200/_cat/health?v # 显示cluster状态 curl -XGET 127.0.0.1:9200/_cluster/health\\?pretty # 列出 master节点 curl -XGET 127.0.0.1:9200/_cat/master?v # 列出节点及利用率 curl -XGET 127.0.0.1:9200/_cat/nodes?v # 显示索引 curl localhost:9200/_cat/indices?v Kibana Kibana是一个开源的数据可视化和分析平台，与Elasticsearch紧密集成。它提供了一个直观的Web界面，让用户能够轻松地探索、分析和可视化存储在Elasticsearch中的数据。\n1 wget https://artifacts.elastic.co/downloads/kibana/kibana-7.17.16-x86_64.rpm Filebeat Filebeat是一个轻量级的开源日志数据收集器，由Elasticsearch提供支持。它专门用于收集、解析和发送日志文件和其他结构化数据到Elasticsearch或Logstash等目标系统进行处理和分析。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 # ============================== Filebeat inputs =============================== filebeat.inputs: # Each - is an input. Most options can be set at the input level, so # you can use different inputs for various configurations. # Below are the input specific configurations. # filestream is an input for collecting log messages from files. - type: filestream # Unique ID among all inputs, an ID is required. id: my-filestream-id # Change to true to enable this input configuration. enabled: false # Paths that should be crawled and fetched. Glob based paths. paths: - /var/log/*.log #- c:\\programdata\\elasticsearch\\logs\\* # ================================== Outputs =================================== # Configure what output to use when sending the data collected by the beat. # ---------------------------- Elasticsearch Output ---------------------------- output.elasticsearch: # Array of hosts to connect to. hosts: [\u0026#34;localhost:9200\u0026#34;] # Protocol - either `http` (default) or `https`. #protocol: \u0026#34;https\u0026#34; EFK on Kubernetes 安装 ElasticSearch 1 2 3 4 5 6 7 8 9 10 11 12 13 # Add the Elastic Helm charts repo helm repo add elastic https://helm.elastic.co # 查询版本 我们使用 7.17.3 [root@master-01 ~]# helm search repo elastic/elasticsearch -l NAME CHART VERSION APP VERSION DESCRIPTION elastic/elasticsearch 8.5.1 8.5.1 Official Elastic helm chart for Elasticsearch elastic/elasticsearch 7.17.3 7.17.3 Official Elastic helm chart for Elasticsearch elastic/elasticsearch 7.17.1 7.17.1 Official Elastic helm chart for Elasticsearch elastic/elasticsearch 7.16.3 7.16.3 Official Elastic helm chart for Elasticsearch elastic/elasticsearch 7.16.2 7.16.2 Official Elastic helm chart for Elasticsearch [root@master-01 ~]# helm pull elastic/elasticsearch --version=7.17.3 修改 values\n安装\n1 2 3 4 5 6 7 8 9 10 [root@master-01 20-log]# helm upgrade --install els -n logging -f elasticsearch/els-values.yaml ./elasticsearch --create-namespace --namespace logging NAME: els LAST DEPLOYED: Sat Nov 18 20:17:57 2023 NAMESPACE: logging STATUS: deployed REVISION: 1 NOTES: 1. Watch all cluster members come up. $ kubectl get pods --namespace=logging -l app=elasticsearch-master -w2. Test cluster health using Helm test. $ helm --namespace=logging test els 集群验证\n1 2 3 4 5 6 7 [root@master-01 20-log]# kubectl get pods --namespace=logging -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES elasticsearch-master-0 1/1 Running 0 99s 10.244.171.24 worker-01 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; elasticsearch-master-1 1/1 Running 0 74s 10.244.184.101 master-01 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; [root@master-01 20-log]# curl 10.244.37.199:9200/_cluster/health?pretty {\u0026#34;cluster_name\u0026#34;:\u0026#34;elasticsearch\u0026#34;,\u0026#34;status\u0026#34;:\u0026#34;green\u0026#34;,\u0026#34;timed_out\u0026#34;:false,\u0026#34;number_of_nodes\u0026#34;:2,\u0026#34;number_of_data_nodes\u0026#34;:2,\u0026#34;active_primary_shards\u0026#34;:1,\u0026#34;active_shards\u0026#34;:2,\u0026#34;relocating_shards\u0026#34;:0,\u0026#34;initializing_shards\u0026#34;:0,\u0026#34;unassigned_shards\u0026#34;:0,\u0026#34;delayed_unassigned_shards\u0026#34;:0,\u0026#34;number_of_pending_tasks\u0026#34;:0,\u0026#34;number_of_in_flight_fetch\u0026#34;:0,\u0026#34;task_max_waiting_in_queue_millis\u0026#34;:0,\u0026#34;active_shards_percent_as_number\u0026#34;:100.0} 安装 Kibana 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 [root@master-01 20-log]# helm search repo elastic/kibana -l NAME CHART VERSION APP VERSION DESCRIPTION elastic/kibana 8.5.1 8.5.1 Official Elastic helm chart for Kibana elastic/kibana 7.17.3 7.17.3 Official Elastic helm chart for Kibana elastic/kibana 7.17.1 7.17.1 Official Elastic helm chart for Kibana # 拉取 chart helm pull elastic/kibana --version=7.17.3 [root@master-01 20-log]# helm -n logging upgrade --install kibana -f kibana/kibana-values.yaml ./kibana NAME: kibana LAST DEPLOYED: Sat Nov 18 20:49:28 2023 NAMESPACE: logging STATUS: deployed REVISION: 1 TEST SUITE: None # 更新 [root@master-01 20-log]# helm -n logging upgrade kibana -f kibana/kibana-values.yaml ./kibana Release \u0026#34;kibana\u0026#34; has been upgraded. Happy Helming! NAME: kibana LAST DEPLOYED: Sat Nov 18 21:14:34 2023 NAMESPACE: logging STATUS: deployed REVISION: 2 TEST SUITE: None 修改 Kibana SVC 使用 NodePort\n1 type: NodePort 安装 Filebeat 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 [root@master-01 20-log]# helm search repo elastic/filebeat -l NAME CHART VERSION APP VERSION DESCRIPTION elastic/filebeat 8.5.1 8.5.1 Official Elastic helm chart for Filebeat elastic/filebeat 7.17.3 7.17.3 Official Elastic helm chart for Filebeat elastic/filebeat 7.17.1 7.17.1 Official Elastic helm chart for Filebeat elastic/filebeat 7.16.3 7.16.3 Official Elastic helm chart for Filebeat [root@master-01 20-log]# helm pull elastic/filebeat --version=7.17.3 [root@master-01 20-log]# helm -n logging install filebeat -f filebeat/filebeat-values-1.yaml ./filebeat NAME: filebeat LAST DEPLOYED: Sat Nov 18 21:53:15 2023 NAMESPACE: logging STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: 1. Watch all containers come up. $ kubectl get pods --namespace=logging -l app=filebeat-filebeat -w 1 2 3 4 5 6 7 8 https://artifacthub.io/packages/helm/elastic/elasticsearch https://artifacthub.io/packages/helm/elastic/kibana https://artifacthub.io/packages/helm/fluent/fluentd helm repo add elastic https://helm.elastic.co 附录： Elasticsearch 基础概念\n集群 Cluster Elasticsearch 集群是一组 Elasticsearch 节点的集合。节点根据用途不同会划分出不同的角色，且节点之间相互通信。Elasticsearch集群常用于处理大规模数据集，目的是实现容错和高可用。Elasticsearch 集群需要一个唯一标识的集群名称来防止不必要的节点加入。\n节点 node 节点是指一个Elasticsearch实例，更确切地说，它是一个Elasticsearch进程。节点可以部署到物理机或者虚拟机上。每当Elasticsearch启动时，节点就会开始运行。每个节点都有唯一标识的名称，在部署多节点集群环境的时候我们要注意不要写错节点名称。\n索引 index 索引是 Elasticsearch 中用于存储和管理相关数据的逻辑容器。索引可以看作数据库中的一个表，它包含了一组具有相似结构的文档。在 Elasticsearch 中，数据以JSON格式的文档存储在索引内。每个索引具有唯一的名称，以便在执行搜索、更新和删除操作时进行引用。索引的名称可以由用户自定义，但必须全部小写。\n分片 shard 分片包含索引数据的一个子集，并且其本身具有完整的功能和独立性，可以将分片近似看作“独立索引“，分片是Elasticsearch 分布式存储的基石，是底层的基本读写单元。分片的目的是分割巨大的索引，将数据分散到集群内各处。\n分片分为主分片和副本分片，一般情况，一个主分片有多个副本分片。主分片负责处理写入请求和存储数据，副本分片只负责存储数据，是主分片的拷贝，文档会存储在具体的某个主分片和副本分片上。\nXpack 安全，开启 elasticsearch 验证 https://www.elastic.co/guide/en/elasticsearch/reference/7.16/security-minimal-setup.html\n","date":"2026-04-06T18:15:04+08:00","image":"https://cdn.jsdelivr.net/gh/luckycloveryh/picgo-bed@main/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260524195222_524_12.jpg","permalink":"https://luckycloveryh.github.io/fuyou/p/k8s-efk-logging-solution/","title":"20 - 日志方案_EFK"},{"content":"Prometheus 架构介绍 Prometheus是一个开源的系统监控和报警框架，其本身也是一个时间序列数据库（Time Series Database，TSDB），它的设计灵感来源于Google的Borgmon，就像Kubernetes是基于Borg系统开源的。\nPrometheus 被称为下一代的监控平台，具有很多和“老牌”监控不一样的特性，比如\n一个多维的数据模型，具有由指标名称和键-值对标识的时间序列数据。 使用PromQL查询和聚合数据，可以非常灵活地对数据进行检索。 不依赖额外的数据存储，Prometheus本身就是一个时序数据库，提供本地存储和分布式存储，并且每个Prometheus都是自治的。 应用程序暴露Metrics接口，Prometheus通过基于HTTP的Pull模型采集数据。 同时可以使用PushGateway进行Push数据。 Prometheus同时支持动态服务和静态配置发现目标机器。 支持多种图形和仪表盘，和Grafana堪称“绝配”。 Prometheus生态系统由多个组件组成，其架构如下：\nPrometheus Server：Prometheus生态最重要的组件，主要用于抓取和存储时间序列数据，同时提供数据的查询和告警策略的配置管理。 Alertmanager：Prometheus生态用于告警的组件，Prometheus Server会将告警发送给Alertmanager，Alertmanager根据路由配置将告警信息发送给指定的人或组。Alertmanager支持邮件、Webhook、微信、钉钉、短信等媒介进行告警通知。 Push Gateway：Prometheus本身是通过Pull的方式拉取数据的，但是有些监控数据可能是短期的，如果没有采集数据可能会出现丢失。Push Gateway可以用来解决此类问题，它可以用来接收数据，也就是客户端可以通过Push的方式将数据推送到PushGateway，之后Prometheus可以通过Pull拉取该数据。 Exporter：主要用来采集监控数据，比如主机的监控数据可以通过node_exporter采集，MySQL的监控数据可以通过mysql_exporter采集，之后Exporter暴露一个接口，比如/metrics，Prometheus可以通过该接口采集到数据。 PromQL：PromQL其实不算Prometheus的组件，它是用来查询数据的一种语法，比如可以通过SQL语句查询数据库的数据，通过LogQL语句查询Loki的数据，通过PromQL语句查询Prometheus的数据。 Service Discovery：用来发现监控目标的自动发现，常用的有基于Kubernetes、Consul、Eureka、文件的自动发现等。 Grafana：用于展示数据，便于数据的查询和观测。 Prometheus 安装 Prometheus 有多种安装方式，比如二进制安装、容器安装和 Kubernetes 集群中安装，将 Prometheus 安装到Kubernetes 集群中也是官方推荐的部署方式。\n将Prometheus安装到Kubernetes集群也有很多方式，比如 Helm、Operator 等，Prometheus 也是支持上述安装方式的。但是Prometheus是一个生态系统，有很多组件都需要安装，并且也有很多监控需要单独配置，于是Prometheus 官方开源了一个 kube-prometheus 项目，该项目不仅仅是用来安装 Prometheus 的，也包含很多其他的组件，如下所示：\nPrometheus Operator 高可用的 Prometheus 高可用的 Alertmanager 主机监控 Node Exporter Prometheus Adapter 容器监控 kube-state-metrics 图形化展示 Grafana 具体可以通过``https://github.com/prometheus-operator/kube-prometheus/``找到该项目进行查看。有了kube-prometheus项目，安装也变得非常简单，只需要两条命令即可。\n首先需要通过该项目地址找到和自己Kubernetes版本对应的Kube Prometheus Stack的版本，我们使用的 kubernetes 1.32.2 ，那么对应的 Kube Prometheus Stack 版本是 release-0.15 。\n从 github 下载 对应分支的代码\n1 2 3 4 5 6 7 # 下载源码 # git clone -b release-0.15 https://github.com/prometheus-operator/kube-prometheus.git # 创建命名空间和CR kubectl create -f manifests-orig/setup # 部署相关组件 kubectl apply -f manifests-orig/ 查看 Prometheus 容器的状态\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [root@master-01 ~]# kubectl get pods -n monitoring NAME READY STATUS RESTARTS AGE alertmanager-main-0 2/2 Running 0 41h alertmanager-main-1 2/2 Running 0 41h alertmanager-main-2 2/2 Running 0 41h blackbox-exporter-d989f64d9-9x7dn 3/3 Running 0 41h grafana-8665b584b-bb2jr 1/1 Running 0 41h kube-state-metrics-76ddfbb447-j7clv 3/3 Running 0 41h node-exporter-bcp2h 2/2 Running 0 41h node-exporter-gn26s 2/2 Running 0 41h node-exporter-xw6p7 2/2 Running 0 41h prometheus-adapter-599c88b6c4-6b8vv 1/1 Running 0 41h prometheus-adapter-599c88b6c4-s62n2 1/1 Running 0 41h prometheus-k8s-0 2/2 Running 0 41h prometheus-k8s-1 2/2 Running 0 41h prometheus-operator-6b64df5498-6xrxt 2/2 Running 0 41h 为了后期更新方便，这里按照类型把相关对象的资源清单文件 放在不同的目录下，命令如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 mkdir alertmanager grafana kubeStateMetrics nodeExporter prometheusAdapter prometheusOperator prometheus blackboxExporter kubernetesControlPlane mv alertmanager-* alertmanager mv grafana-* grafana mv kubeStateMetrics-* kubeStateMetrics mv nodeExporter-* nodeExporter mv prometheusAdapter-* prometheusAdapter mv prometheusOperator-* prometheusOperator mv prometheus-* prometheus mv blackboxExporter-* blackboxExporter mv kubernetesControlPlane-* kubernetesControlPlane [root@master-01 manifests]# ll total 40 drwxr-xr-x 2 root root 313 Dec 19 16:28 alertmanager drwxr-xr-x 2 root root 4096 Dec 12 16:13 blackboxExporter drwxr-xr-x 2 root root 4096 Dec 19 16:32 grafana -rw-r--r-- 1 root root 4361 Aug 8 00:12 kubePrometheus-prometheusRule.yaml drwxr-xr-x 2 root root 4096 Dec 12 16:13 kubernetesControlPlane drwxr-xr-x 2 root root 4096 Dec 12 16:12 kubeStateMetrics drwxr-xr-x 2 root root 314 Dec 12 16:12 nodeExporter drwxr-xr-x 2 root root 4096 Dec 19 16:35 prometheus drwxr-xr-x 2 root root 4096 Dec 12 16:12 prometheusAdapter drwxr-xr-x 2 root root 4096 Dec 12 16:12 prometheusOperator drwxr-xr-x 2 root root 4096 Aug 8 00:12 setup 安装成功后就可以访问 Grafana 和 Prometheus Web UI\n1 2 3 4 [root@master-01 manifests]# kubectl -n monitoring get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE grafana NodePort 10.100.55.197 \u0026lt;none\u0026gt; 3000:30030/TCP 24h 24h prometheus-k8s NodePort 10.100.108.119 \u0026lt;none\u0026gt; 9090:30090/TCP,8080:31180/TCP 24h Grafana 和 Prometheus Web UI 默认使用 ClusterIP，我们需要将SVC的类型修改为 NodePort。\nGrafana 默认用户名、密码均为 admin\nKube-promethues 项目默认已经添加了常用的监控目标。\n云原生应用监控 监控数据来源 从Prometheus的架构中了解到，Prometheus通常采用Pull的形式来拉取数据，也就意味着被监控应用只要有一个能获取到监控数据的接口，就可以采集到监控数据。\n基于云原生理念开发的程序自己会暴露Metrics接口，就像Kubernetes本身的组件、Etcd等，都有一个/metrics接口，Prometheus 只需要请求这个接口即可获取到相关数据。\n什么是 ServiceMonitor 如果使用二进制的方式安装 Prometheus，用户需要通过 Prometheus 的一个配置文件来配置需要监控哪些数据，或者配置一些告警策略。这个配置文件的维护非常麻烦，特别是监控项非常多的情况下，很容易出现配置错误，而在Kubernetes上部署Prometheus，可以不用去维护这个配置文件，而是通过一个叫ServiceMonitor 的资源来自动发现监控目标并动态生成配置。\n监控 example-app 应用 部署应用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 apiVersion: apps/v1 kind: Deployment metadata: name: example-app spec: replicas: 3 selector: matchLabels: app: example-app template: metadata: labels: app: example-app spec: containers: - name: example-app image: registry.cn-beijing.aliyuncs.com/xxhf/instrumented_app ports: - name: web containerPort: 8080 为应用创建 SVC 1 2 3 4 5 6 7 8 9 10 11 12 kind: Service apiVersion: v1 metadata: name: example-app labels: app: example-app spec: selector: app: example-app ports: - name: web port: 8080 创建 serviceMonitor 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: example-app labels: team: frontend app.kubernetes.io/part-of: kube-prometheus spec: jobLabel: app.kubernetes.io/name selector: matchLabels: app: example-app namespaceSelector: matchNames: - default endpoints: - port: web 验证 监控 etcd 创建 etcd svc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 apiVersion: v1 kind: Service metadata: labels: app: kube-etcd name: kube-etcd namespace: kube-system spec: ports: - name: http-metrics port: 2381 protocol: TCP targetPort: 2381 selector: component: etcd type: ClusterIP 创建 serviceMonitor 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: kube-etcd namespace: kube-system labels: app.kubernetes.io/part-of: kube-prometheus spec: jobLabel: component selector: matchLabels: app: kube-etcd namespaceSelector: matchNames: - kube-system endpoints: - port: http-metrics 验证抓取目标是否生效 1 2 3 推荐修改/etc/kubernetes/manifests/kube-controller-manager.yaml /etc/kubernetes/manifests/kube-scheduler.yaml /etc/kubernetes/manifests/etcd.yaml 为 etcd 配置 dashboard 非云原生应用监控 配置 mysql-exporter 非云原生应用的监控需要安装对应的 exporter，我们看一下如何实现 MySQL 的监控。\n安装 mysql-exporter 1 2 3 4 wget https://github.com/prometheus/mysqld_exporter/releases/download/v0.16.0/mysqld_exporter-0.16.0.linux-amd64.tar.gz tar xvf mysqld_exporter-0.16.0.linux-amd64.tar.gz -C /usr/local/ ln -s /usr/local/mysqld_exporter-0.16.0.linux-amd64 /usr/local/mysqld_exporter cd /usr/local/mysqld_exporter 创建授权 MySQL\n1 2 3 4 5 6 7 8 9 10 11 12 dnf -y install mariadb-server mariadb systemctl enable --now mariadb CREATE USER \u0026#39;exporter\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED BY \u0026#39;UPKeH%EsT4syjA\u0026#39; WITH MAX_USER_CONNECTIONS 3; GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO \u0026#39;exporter\u0026#39;@\u0026#39;localhost\u0026#39;; nohup ./mysqld_exporter \u0026amp; curl 192.168.5.110:9104/metrics MariaDB\n连接 mysql 需要用户名、密码，所以下载之后，首先要创建配置文件，把用户名、密码以及mysql服务器地址，这些基本信息填进去。\n在 mysqld_exporter 的目录下，创建一个 .my.cnf 的文件，内容参考下面的内容：\n1 2 3 4 5 [client] host=127.0.0.1 port=3306 user=exporter password=3HdexTlk3nOw0k!3Nji 启动 mysql_exporter 1 ./mysqld_exporter mysql_exporter 默认监听 9104 端口，请求 mysql_exporter 对外暴露的 metrics 接口。\n1 2 3 4 5 6 7 [root@db-srv mysqld_exporter]# curl 127.0.0.1:9104/metrics # HELP go_gc_duration_seconds A summary of the wall-time pause (stop-the-world) duration in garbage collection cycles. # TYPE go_gc_duration_seconds summary go_gc_duration_seconds{quantile=\u0026#34;0\u0026#34;} 4.5418e-05 go_gc_duration_seconds{quantile=\u0026#34;0.25\u0026#34;} 4.7015e-05 go_gc_duration_seconds{quantile=\u0026#34;0.5\u0026#34;} 4.8137e-05 ... ... 配置 抓取目标 我们需要在 Prometheus 配置文件中添加一个 Job，用来配置抓取 mysql-exporter metric endpoint， 有两种配置方式，一种是使用 prometheus-operator 提供 CRD ScrapeConfig ，另一种方法是让prometheus 加载外部配置文件。\ntar/monitor/19-monitor/kube-prometheus/manifests/custom-config\n第一种方法：\n修改 prometheus\n1 2 3 4 spec: scrapeConfigSelector: matchLabels: prometheus: system-monitoring-prometheus 添加抓取目标\n1 2 3 4 5 6 7 8 9 10 11 12 13 apiVersion: monitoring.coreos.com/v1alpha1 kind: ScrapeConfig metadata: name: static-config namespace: monitoring labels: prometheus: system-monitoring-prometheus spec: staticConfigs: - labels: job: mysql targets: - 192.168.11.42:9104 验证\n第二种方法：\n配置 prometheus 抓取 mysqld_exporter 暴露的 metrics 信息。\n我们前面配置监控目标时，用的都是ServiceMonitor，但是ServiceMonitor可能会有一些限制。比如，如果没有安装Prometheus Operator，可能就无法使用ServiceMonitor，另外并不是所有的监控都能使用ServiceMonitor进行配置，或者使用ServiceMonitor配置显得过于繁琐。此处我们使用 prometheus 默认的配置文件，添加一个抓取mysql 的 job。\n首先创建一个空文件，然后通过该文件创建一个 Secret，这个 Secret 可作为 Prometheus 的静态配置：\n1 2 $ touch additional-scrape-configs.yaml $ kubectl -n monitoring create secret generic additional-scrape-configs --from-file=additional-scrape-configs.yaml 创建完 Secret后，需要编辑 Prometheus 的配置，加载我们自己创建的外部配置文件 。\n1 kubectl edit prometheus -n monitoring k8s 添加以下 配置文件 ，不需要重启 Prometheus 即可生效。之后在 additional-scrape-configs.yaml 文件内添加我们需要的静态配置即可。\n1 2 3 4 spec: additionalScrapeConfigs: name: additional-scrape-configs # secret-name key: additional-scrape-configs.yaml # key 1 2 3 4 5 additional-scrape-configs.yaml - job_name: mysql static_configs: - targets: - 192.168.11.42:9104 可以看到此处的内容和传统配置的内容一致，只需要添加对应的job即可。之后通过该文件更新该 Secret：\n1 # kubectl -n monitoring create secret generic additional-scrape-configs --from-file=additional-scrape-configs.yaml --dry-run=client -o yaml | kubectl -n monitoring apply -f - 在 prometheus 的配置文件中可以验证 job 已经添加成功。\n在 Targets 中也可以看到 mysql\n最后在 Grafana 中创建 Dashboard，导入 MySQL Dashboard 14057 。\nBlackbox 黑盒监控 对 Etcd 或MySQL 的监控是监控应用本身，也就是程序内部的一些指标，这类监控关注的是原因，一般为出现问题的根本原因，此类监控称为白盒监控。还有一类监控关注的是现象，比如某个网站突然慢了，或者打不开了。此类告警是站在用户的角度看到的东西，比较关注现象，表示正在发生的问题，这类监控称为黑盒监控。\n白盒监控可以通过Exporter采集数据，黑盒监控也可以通过Exporter采集数据，新版本的PrometheusStack已经默认安装了Blackbox Exporter，可以用其采集某个域名、接口或者TCP连接的状态、是否可用等。\nPrometheus 静态配置 前面几个小节配置监控目标时，用的都是ServiceMonitor，但是ServiceMonitor可能会有一些限制。比如，如果没有安装Prometheus Operator，可能就无法使用ServiceMonitor，另外并不是所有的监控都能使用ServiceMonitor进行配置，或者使用ServiceMonitor配置显得过于繁琐。\n在 additional-scrape-configs.yaml 文件内继续添加以下静态配置，用于黑盒监控的配置：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 - job_name: \u0026#39;blackbox-web\u0026#39; metrics_path: /probe params: module: [http_2xx] # Look for a HTTP 200 response. static_configs: - targets: - http://prometheus.io # Target to probe with http. - https://prometheus.io # Target to probe with https. - http://www.xinxianghf.cn relabel_configs: - source_labels: [__address__] target_label: __param_target - source_labels: [__param_target] target_label: instance - target_label: __address__ replacement: blackbox-exporter:19115 # The blackbox exporter\u0026#39;s real hostname:port. 可以看到此处的内容和传统配置的内容一致，只需要添加对应的job即可。之后通过该文件更新该Secret：\n1 kubectl -n monitoring create secret generic additional-scrape-configs --from-file=additional-scrape-configs.yaml --dry-run=client -o yaml | kubectl -n monitoring replace -f - 验证配置文件 是否生效\n检查 Targets\n登录 Grafana，导入Dashboard 13659，导入完成后，稍等一分钟即可在 Prometheus Web UI 看到该配置。\n配置告警 从配置文件可以看出，Alertmanager 的配置主要分为5大块：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 global: # The smarthost and SMTP sender used for mail notifications. smtp_smarthost: \u0026#39;localhost:25\u0026#39; smtp_from: \u0026#39;alertmanager@example.org\u0026#39; smtp_auth_username: \u0026#39;alertmanager\u0026#39; smtp_auth_password: \u0026#39;password\u0026#39; # The directory from which notification templates are read. templates: - \u0026#39;/etc/alertmanager/template/*.tmpl\u0026#39; # The root route on which each incoming alert enters. route: group_by: [\u0026#39;job\u0026#39;, \u0026#39;namespace\u0026#39;, \u0026#39;alertname\u0026#39;] group_wait: 30s group_interval: 5m repeat_interval: 3h # A default receiver receiver: default-team-mails # All the above attributes are inherited by all child routes and can # overwritten on each. # The child route trees. routes: - receiver: team-X-pager match_re: job: node_exporter continue: true - receiver: team-X-pager match_re: job: mysqld_exporter continue: true - receiver: team-DB-pager match_re: job: .* continue: true inhibit_rules: - source_matchers: [severity=\u0026#34;critical\u0026#34;] target_matchers: [severity=\u0026#34;warning\u0026#34;] equal: [alertname, cluster, service] receivers: - name: \u0026#39;team-X-mails\u0026#39; email_configs: - to: \u0026#39;team-X+alerts@example.org\u0026#39; - name: \u0026#39;team-X-pager\u0026#39; email_configs: - to: \u0026#39;team-X+alerts-critical@example.org\u0026#39; - name: \u0026#39;team-Y-pager\u0026#39; pagerduty_configs: - service_key: \u0026lt;team-Y-key\u0026gt; - name: \u0026#39;team-DB-pager\u0026#39; pagerduty_configs: - service_key: \u0026lt;team-DB-key\u0026gt; Global：全局配置，主要进行一些通用的配置，比如邮件通知的账号、密码、SMTP服务器、微信告警等。Global块配置下的配置选项在本配置文件内的所有配置项下可见，但是文件内其他位置的子配置可以覆盖Global配置。 Templates：用于放置自定义模板的位置。 Route：告警路由配置，用于告警信息的分组路由，可以将不同分组的告警发送给不同的收件人。比如将数据库告警发送给DBA，服务器告警发送给OPS。 Inhibit_rules：告警抑制，主要用于减少告警的次数，防止“告警轰炸”。比如某个宿主机宕机，可能会引起容器重建、漂移、服务不可用等一系列问题，如果每个异常均有告警，会一次性发送很多告警，造成告警轰炸，并且也会干扰定位问题的思路，所以可以使用告警抑制，屏蔽由宿主机宕机引来的其他问题，只发送宿主机宕机的消息即可。 Receivers：告警收件人配置，每个receiver都有一个名字，经过route分组并且路由后需要指定一个receiver，就在此处配置。 路由规则 Alertmanager 的配置比较复杂且经常需要变更，我们详细看一下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 route: group_by: [\u0026#39;job\u0026#39;, \u0026#39;namespace\u0026#39;, \u0026#39;alertname\u0026#39;] group_wait: 30s group_interval: 5m repeat_interval: 3h # A default receiver receiver: default-team-mails # The child route trees. routes: - receiver: team-X-pager match_re: job: node_exporter continue: true - receiver: team-X-pager match_re: job: mysqld_exporter continue: true - receiver: team-DB-pager match_re: job: .* continue: true 从配置文件可以看出，路由配置块的顶级配置由route开始，它是整个路由的入口，称作根路由。每一条告警进来后，都先进入route，之后根据告警自身的标签和route.group_by配置的字段进行分组。比如可以根据job、alertname或者其他自定义的标签名称进行分组，分组后进入子路由（通过route.routes配置子路由），进一步进行更加细粒度的划分，比如job名称包含mysql的发送给DBA组。\nRoute 常用的配置：\nreceiver：告警的通知目标，需要和receivers配置中的name进行匹配。需要注意的是，route.routes下也可以有receiver配置，优先级高于route.receiver配置的默认接收人，当告警没有匹配到子路由时，会使用route.receiver进行通知，比如上述配置中的 default-team-mails。 group_by：分组配置，值类型为列表。比如配置成[\u0026lsquo;job\u0026rsquo;, \u0026lsquo;severity\u0026rsquo;]，代表告警信息包含job和severity标签的会进行分组，且标签的key和value都相同才会被分到一组。 continue：决定匹配到第一个路由后，是否继续后续匹配。默认为false，即匹配到第一个子节点后停止继续匹配。 match：一对一匹配规则，比如match配置的为job: mysql，那么具有job=mysql的告警会进入该路由。 match_re：和match类似，只不过match_re是正则匹配。 matchers：这是Alertmanager 0.22版本新添加的一个配置项，用于替换match和match_re。 group_wait：告警通知等待，值类型为字符串。若一组新的告警产生，则会等group_wait后再发送通知，该功能主要用于当告警在很短时间内接连产生时，在group_wait内合并为单一的告警后再发送，防止告警过多，默认值为30s。 group_interval：同一组告警通知后，如果有新的告警添加到该组中，再次发送告警通知的时间，默认值为5m。 repeat_interval：如果一条告警通知已成功发送，且在间隔repeat_interval后，该告警仍然未被设置为resolved，则会再次发送该告警通知，默认值为4h。 以上即为Alertmanager常用的路由配置，可以看到 Alertmanager 的路由和匹配规则非常灵活，通过不同的路由嵌套和匹配规则可以达到不同的通知效果。\n配置钉钉告警 Alertmanager 的配置文件是通过 Secret 进行存储的，其原始文件为 alertmanager-secret.yaml，内容大致如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 apiVersion: v1 kind: Secret metadata: labels: app.kubernetes.io/component: alert-router app.kubernetes.io/instance: main app.kubernetes.io/name: alertmanager app.kubernetes.io/part-of: kube-prometheus app.kubernetes.io/version: 0.28.1 name: alertmanager-main namespace: monitoring stringData: alertmanager.yaml: |- \u0026#34;global\u0026#34;: \u0026#34;resolve_timeout\u0026#34;: \u0026#34;5m\u0026#34; \u0026#34;receivers\u0026#34;: - \u0026#34;name\u0026#34;: \u0026#34;dingtalk\u0026#34; webhook_configs: - url: http://192.168.11.58:8060/dingtalk/webhook/send send_resolved: true \u0026#34;route\u0026#34;: \u0026#34;group_by\u0026#34;: - \u0026#34;namespace\u0026#34; - \u0026#34;job\u0026#34; - \u0026#34;alertname\u0026#34; \u0026#34;group_interval\u0026#34;: \u0026#34;5m\u0026#34; \u0026#34;group_wait\u0026#34;: \u0026#34;30s\u0026#34; \u0026#34;receiver\u0026#34;: \u0026#34;dingtalk\u0026#34; \u0026#34;repeat_interval\u0026#34;: \u0026#34;12h\u0026#34; \u0026#34;routes\u0026#34;: - receiver: \u0026#39;dingtalk\u0026#39; matchers: - alertname =~ \u0026#34;InfoInhibitor|Watchdog\u0026#34; type: Opaque 修改后重新应用， alertmanager 会自动加载新的配置文件 。\n1 2 3 4 5 kubectl apply -f alertmanager-secret.yaml 修改之后需要去webhook配置文件中配置config.yaml配置文件的报警模版和关键词 等一会就可以在钉钉上收到告警消息了。\nPrometheus 高可用 多 prometheus 实例 Thanos VictoriaMetrics ","date":"2026-04-06T18:12:54+08:00","image":"https://cdn.jsdelivr.net/gh/luckycloveryh/picgo-bed@main/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260524195220_523_12.jpg","permalink":"https://luckycloveryh.github.io/fuyou/p/k8s-kube-prometheus-deployment/","title":"19-监控 - kube-prometheus 部署"},{"content":"Metrics Server 如果你对 Linux 系统有所了解的话，应该知道有一个命令 top 能够实时显示当前系统的 CPU 和内存利用率，它是性能分析和调优的基本工具，非常有用。Kubernetes 也提供了类似的命令，就是 kubectl top，不过默认情况下这个命令不会生效，必须要安装一个插件 Metrics Server 才可以。\nMetrics Server 是一个专门用来收集 Kubernetes 核心资源指标（metrics）的工具，它定时从所有节点的 kubelet 里采集信息，但是对集群的整体性能影响极小，每个节点只大约会占用 1m 的 CPU 和 2MB 的内存，所以性价比非常高。\n下面的这张图来自 Kubernetes 官网，你可以对 Metrics Server 的工作方式有个大概了解： 它调用 kubelet 的 API 拿到节点和 Pod 的指标，再把这些信息交给 apiserver，这样 kubectl、HPA 就可以利用 apiserver 来读取指标了：\nMetrics Server Github 地址 兼容列表：\n1 wget https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.8.1/high-availability-1.21+.yaml 修改YAML 添加 --kubelet-insecure-tls\n部署 1 kubectl apply -f metrics-server-1.21+.yaml 验证 1 2 3 4 5 6 7 8 9 10 11 12 13 14 [root@master-01 19-monitor]# kubectl top node NAME CPU(cores) CPU% MEMORY(bytes) MEMORY% node 204m 10% 1310Mi 69% worker-01 88m 4% 1085Mi 57% [root@master-01 19-monitor]# [root@master-01 19-monitor]# [root@master-01 19-monitor]# [root@master-01 19-monitor]# kubectl top pods NAME CPU(cores) MEMORY(bytes) busybox 0m 0Mi httpserver-d8557894d-4fg29 0m 1Mi n1 0m 2Mi n2 0m 2Mi nginx 0m 2Mi HorizontalPodAutoscaler（HPA） 有了 Metrics Server，我们就可以轻松地查看集群的资源使用状况了，不过它另外一个更重要的功能是辅助实现应用的“水平自动伸缩”。\nKubernetes 为此就定义了一个新的 API 对象，叫做“HorizontalPodAutoscaler”，简称是 “hpa”。顾名思义，它是专门用来自动伸缩 Pod 数量的对象，适用于 Deployment 和 StatefulSet，但不能用于 DaemonSet 对象。\nHorizontalPodAutoscaler 的能力完全基于 Metrics Server，它从 Metrics Server 获取当前应用的运行指标，主要是 CPU 使用率，再依据预定的策略增加或者减少 Pod 的数量。 下面我们就来看看该怎么使用 HorizontalPodAutoscaler，首先要定义 Deployment 和 Service，创建一个 Nginx 应用，作为自动伸缩的目标对象：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 apiVersion: apps/v1 kind: Deployment metadata: name: nginx-hpa labels: app: nginx spec: replicas: 1 selector: matchLabels: app: nginx-hpa template: metadata: labels: app: nginx-hpa spec: containers: - name: nginx image: nginx:1.22.1 ports: - containerPort: 80 resources: requests: cpu: 50m memory: 10Mi limits: cpu: 100m memory: 20Mi --- apiVersion: v1 kind: Service metadata: name: nginx-hpa-svc spec: ports: - port: 80 protocol: TCP targetPort: 80 selector: app: nginx-hpa 接下来我们要用命令 kubectl autoscale 创建一个 HorizontalPodAutoscaler 的样板 YAML 文件，它有三个参数：\nmin，Pod 数量的最小值，也就是缩容的下限。 max，Pod 数量的最大值，也就是扩容的上限。 cpu-percent，CPU 使用率指标，当大于这个值时扩容，小于这个值时缩容。 好，现在我们就来为刚才的 Nginx 应用创建 HorizontalPodAutoscaler，指定 Pod 数量最少 2 个，最多 10 个，CPU 使用率指标设置的小一点，5%，方便我们观察扩容现象：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: nginx-hpa spec: maxReplicas: 10 minReplicas: 2 scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: nginx-hpa metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 5 HorizontalPodAutoscaler 会根据 YAML 里的描述，找到要管理的 Deployment，把 Pod 数量调整成 2 个，再通过 Metrics Server 不断地监测 Pod 的 CPU 使用率。\n下面我们来给 Nginx 加上压力流量，运行一个测试 Pod，使用的镜像是“httpd:alpine”，它里面有 HTTP 性能测试工具 ab（Apache Bench）：\n1 2 3 kubectl run test -it --image=httpd:alpine -- sh ab -c 10 -t 60 -n 100000 \u0026#39;http://nginx-hpa-svc/\u0026#39; kubectl get hpa nginx-hpa -w 由于 Metrics Server 大约每 15 秒采集一次数据，所以 HorizontalPodAutoscaler 的自动化扩容和缩容也是按照这个时间点来逐步处理的。\n当它发现目标的 CPU 使用率超过了预定的 5% 后，就会以 2 的倍数开始扩容，一直到数量上限，然后持续监控一段时间，如果 CPU 使用率回落，就会再缩容到最小值。\nkube-prometheus 组件 Kube-prometheus 包含的组件:\nThe Prometheus Operator Highly available Prometheus Highly available Alertmanager Prometheus node-exporter Prometheus Adapter for Kubernetes Metrics APIs kube-state-metrics Grafana Operator Operator Operator 是由 CoreOS 开发的，用来扩展 Kubernetes API，特定的应用程序控制器，它用来创建、配置和管理复杂的有状态应用，如数据库、缓存和监控系统。Operator 基于 Kubernetes 的资源和控制器概念之上构建，但同时又包含了应用程序特定的领域知识。创建 Operator 的关键是 CRD（自定义资源）的设计。\nKubernetes 1.7 版本以来就引入了自定义控制器的概念，该功能可以让开发人员扩展添加新功能，更新现有的功能，并且可以自动执行一些管理任务，这些自定义的控制器就像 Kubernetes 原生的组件一样，Operator 直接使用 Kubernetes API 进行开发，也就是说他们可以根据这些控制器内部编写的自定义规则来监控集群、更改 Pods/Services、对正在运行的应用进行扩缩容。\nCR CR 代表 自定义资源（Custom Resource）是一种扩展 Kubernetes API 的方式，允许用户定义自己的资源类型和规范。它们允许用户在Kubernetes中创建和管理自定义资源对象，这些对象可以与Kubernetes核心资源（如Pod、Service、Deployment等）一样进行操作。\nCRD CRD（Custom Resource Definition）是一种 Kubernetes 资源，用于定义 CR 的结构和行为。通过创建CRD对象，用户可以定义新的 CR 类型，包括它们的API结构、字段、验证规则和操作行为。\n通过使用CR和CRD，用户可以扩展Kubernetes的功能，以适应特定的应用需求。它们提供了一种自定义资源的机制，使用户能够以声明的方式定义和操作自己的资源类型，而无需修改Kubernetes核心代码。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: # 名字必需与下面的 spec 字段匹配，并且格式为 \u0026#39;\u0026lt;名称的复数形式\u0026gt;.\u0026lt;组名\u0026gt;\u0026#39; name: crontabs.stable.example.com spec: # 组名称，用于 REST API: /apis/\u0026lt;组\u0026gt;/\u0026lt;版本\u0026gt; group: stable.example.com # 列举此 CustomResourceDefinition 所支持的版本 versions: - name: v1 # 每个版本都可以通过 served 标志来独立启用或禁止 served: true # 其中一个且只有一个版本必需被标记为存储版本 storage: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: cronSpec: type: string image: type: string replicas: type: integer # 可以是 Namespaced 或 Cluster scope: Namespaced names: # 名称的复数形式，用于 URL：/apis/\u0026lt;组\u0026gt;/\u0026lt;版本\u0026gt;/\u0026lt;名称的复数形式\u0026gt; plural: crontabs # 名称的单数形式，作为命令行使用时和显示时的别名 singular: crontab # kind 通常是单数形式的驼峰命名（CamelCased）形式。你的资源清单会使用这一形式。 kind: CronTab # shortNames 允许你在命令行使用较短的字符串来匹配资源 shortNames: - ct 声明一个 CronTab 对象\n1 2 3 4 5 6 7 8 9 10 apiVersion: \u0026#34;stable.example.com/v1\u0026#34; kind: CronTab metadata: name: my-new-cron-object spec: cronSpec: \u0026#34;* * * * */5\u0026#34; image: my-awesome-cron-image [root@node-01 CRD]# kubectl get ct NAME AGE my-new-cron-object 6h32m Prometheus Operator Prometheus Operator是一种为 Kubernetes 提供本地部署和管理 Prometheus 及相关监控组件的工具。该项目的目的是简化和自动化为 Kubernetes 集群配置基于 Prometheus 的监控堆栈。\nPrometheus Operator 包括但不限于以下功能：\nKubernetes自定义资源：使用Kubernetes自定义资源来部署和管理Prometheus、Alertmanager和相关组件。通过定义自定义资源对象（CRD），可以在Kubernetes中声明和配置 Prometheus 实例、Alertmanager 实例等。 简化的部署配置：通过本地的Kubernetes资源，可以配置Prometheus的基本设置，如版本、持久化、数据保留策略和副本数。这样可以更方便地进行部署配置，无需涉及复杂的配置文件。 Prometheus 目标配置：基于熟悉的Kubernetes标签查询，自动生成监控目标的配置。无需学习Prometheus特定的配置语言，即可快速定义要监控的目标。这样可以简化监控目标的配置过程。 安装 Operator 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 wget https://github.com/prometheus-operator/prometheus-operator/releases/download/v0.69.1/bundle.yaml [root@master-01 ~]# kubectl create -f bundle.yaml customresourcedefinition.apiextensions.k8s.io/alertmanagerconfigs.monitoring.coreos.com created customresourcedefinition.apiextensions.k8s.io/alertmanagers.monitoring.coreos.com created customresourcedefinition.apiextensions.k8s.io/podmonitors.monitoring.coreos.com created customresourcedefinition.apiextensions.k8s.io/probes.monitoring.coreos.com created customresourcedefinition.apiextensions.k8s.io/prometheusagents.monitoring.coreos.com created customresourcedefinition.apiextensions.k8s.io/prometheuses.monitoring.coreos.com created customresourcedefinition.apiextensions.k8s.io/prometheusrules.monitoring.coreos.com created customresourcedefinition.apiextensions.k8s.io/scrapeconfigs.monitoring.coreos.com created customresourcedefinition.apiextensions.k8s.io/servicemonitors.monitoring.coreos.com created customresourcedefinition.apiextensions.k8s.io/thanosrulers.monitoring.coreos.com created clusterrolebinding.rbac.authorization.k8s.io/prometheus-operator created clusterrole.rbac.authorization.k8s.io/prometheus-operator created deployment.apps/prometheus-operator created serviceaccount/prometheus-operator created service/prometheus-operator created Prometheus Operator 在Kubernetes中引入了自定义资源，用于声明 Prometheus 和 Alertmanager 集群的期望状态以及Prometheus的配置。\nPrometheus：Prometheus自定义资源用于定义Prometheus实例的配置和规范。可以指定版本、持久化配置、存储策略、副本数等参数，以及与其他资源的关联关系。 ServiceMonitor：ServiceMonitor 是一个自定义资源，用于定义要由Prometheus监控的服务和指标。可以指定服务的标签选择器，以便Prometheus可以动态地发现和监控符合条件的服务。 Alertmanager：Alertmanager 自定义资源用于定义Alertmanager实例的配置和规范。可以指定接收告警的通知渠道、告警路由的规则等参数。 Prometheus 资源以声明方式描述了 Prometheus 部署的期望状态，而 ServiceMonitor 和 PodMonitor 资源描述了Prometheus 要监控的目标。\n部署测试应用 我们部署一个简单的测试应用，起了 3 个副本，通过8080端口对外映射metrics指标信息。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 apiVersion: apps/v1 kind: Deployment metadata: name: example-app spec: replicas: 3 selector: matchLabels: app: example-app template: metadata: labels: app: example-app spec: containers: - name: example-app image: registry.cn-beijing.aliyuncs.com/xxhf/instrumented_app ports: - name: web containerPort: 8080 创建对应的 service 对象。\n1 2 3 4 5 6 7 8 9 10 11 12 kind: Service apiVersion: v1 metadata: name: example-app labels: app: example-app spec: selector: app: example-app ports: - name: web port: 8080 创建 ServiceMonitor 对象 1 2 3 4 5 6 7 8 9 10 11 12 apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: example-app labels: team: frontend spec: selector: matchLabels: app: example-app endpoints: - port: web 部署 Prometheus 创建RBAC 策略\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 apiVersion: v1 kind: ServiceAccount metadata: name: prometheus --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: prometheus rules: - apiGroups: [\u0026#34;\u0026#34;] resources: - nodes - nodes/metrics - services - endpoints - pods verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;watch\u0026#34;] - apiGroups: [\u0026#34;\u0026#34;] resources: - configmaps verbs: [\u0026#34;get\u0026#34;] - apiGroups: - networking.k8s.io resources: - ingresses verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;watch\u0026#34;] - nonResourceURLs: [\u0026#34;/metrics\u0026#34;] verbs: [\u0026#34;get\u0026#34;] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: prometheus roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: prometheus subjects: - kind: ServiceAccount name: prometheus namespace: default Prometheus自定义资源定义了底层的具体StatefulSet的特性（副本数、资源请求/限制等），同时通过spec.serviceMonitorSelector字段指定应包含哪些ServiceMonitor。\n之前我们创建了一个带有team: frontend标签的 ServiceMonitor 对象，现在我们在Prometheus对象中定义，Prometheus应选择所有带有team: frontend标签的ServiceMonitor。这使得前端团队可以创建新的ServiceMonitors和Services，而无需重新配置Prometheus对象。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 apiVersion: monitoring.coreos.com/v1 kind: Prometheus metadata: name: prometheus spec: replicas: 1 serviceAccountName: prometheus serviceMonitorSelector: matchLabels: team: frontend resources: requests: memory: 400Mi enableAdminAPI: false 将对象部署到集群中\n1 2 kubectl apply -f prometheus.yaml kubectl -n default get prometheus prometheus -w 暴露 Prometheus SVC 要访问Prometheus界面，需要将Prometheus服务暴露给外部。为了简单起见，我们可以使用一个NodePort类型的Service来实现。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 apiVersion: v1 kind: Service metadata: name: prometheus spec: type: NodePort ports: - name: web nodePort: 30900 port: 9090 protocol: TCP targetPort: web selector: prometheus: prometheus 附录 metric server vs kube-state-metric vs cAdvisor Metric Server（指标服务器）： Metric Server是Kubernetes的核心组件之一，用于收集和聚合集群级别的资源利用率指标，如CPU使用率、内存使用量等。 Metric Server使用Kubernetes API提供的Metrics API来获取和暴露指标数据，可以通过kubectl命令或API查询这些指标。 Metric Server通常用于水平扩展Pod的自动伸缩、资源配额管理和监控集群整体的资源利用率。 kube-state-metrics（Kubernetes 状态指标）KSM： kube-state-metrics 是一个独立的监控工具，用于收集和暴露Kubernetes集群中的各种资源对象的状态指标。 kube-state-metrics 通过解析 Kubernetes 的 API 服务器中的资源对象状态，生成与资源对象相关的指标数据，如节点数量、Pod 数量、ReplicaSet 数量等。 kube-state-metrics 提供了丰富的资源指标，可以用于监控和告警，以及与 Prometheus 等监控系统集成。 cAdvisor（容器监控代理）： cAdvisor 是一个容器监控代理，用于收集和暴露单个节点上容器级别的资源指标。它是Kubernetes默认集成的监控工具之一。 cAdvisor 运行在每个节点上，通过直接与Docker或其他容器运行时交互，收集容器的CPU、内存、磁盘和网络等资源使用情况。 cAdvisor 提供了实时的容器级别指标，可以用于监控容器的性能和资源利用情况。 修复 unhealthy target etcd.yaml 1 2 3 - --listen-metrics-urls=http://127.0.0.1:2381 # 修改为 - --listen-metrics-urls=http://0.0.0.0:2381 kube-controller-manager.yaml 1 2 3 - --bind-address=127.0.0.1 # 修改为 - --bind-address=0.0.0.0 kube-scheduler.yaml 1 2 3 - --bind-address=127.0.0.1 # 修改为 - --bind-address=0.0.0.0 Kube-proxy kubectl -n kube-system edit cm kube-proxy\n1 2 3 metricsBindAddress: \u0026#34;\u0026#34; # 修改为 metricsBindAddress: \u0026#34;0.0.0.0\u0026#34; 重启 kube-proxy\n全栈监控 可观测 全栈监控（Full Stack Monitoring）是一种综合性的监控方案，用于收集、分析和可视化应用程序的各个层面和组件的性能和健康状态。它提供了对整个应用程序栈的端到端可见性，包括前端、后端、基础设施和用户体验等方面。\n全栈监控旨在帮助开发团队、运维团队和业务团队全面了解应用程序的运行状况，以便及时发现和解决问题，并持续改进应用程序的性能和可用性。通过收集和分析各个组件的监控数据，全栈监控可以提供以下方面的信息：\n应用程序性能：全栈监控可以实时监测应用程序的性能指标，例如响应时间、吞吐量、错误率等。这些指标可以帮助识别性能瓶颈和优化机会，从而提升应用程序的效率和用户体验。 用户体验：全栈监控可以跟踪用户在应用程序中的行为和交互，并提供关于用户体验的指标，如页面加载时间、交互延迟等。这些指标可以帮助了解用户对应用程序的满意度，并识别可能影响用户体验的问题。 服务依赖关系：应用程序通常依赖于多个服务和组件。全栈监控可以跟踪这些依赖关系，并提供服务之间的关联性和性能指标。这有助于识别服务之间的依赖问题、故障传播和性能影响。 基础设施监控：全栈监控可以监控应用程序运行所需的基础设施层，包括服务器、数据库、网络等。它可以提供有关基础设施的指标和警报，以确保其正常运行和可用性。 日志和错误跟踪：全栈监控可以收集应用程序的日志和错误信息，并提供对这些信息的分析和可视化。这有助于快速定位和解决应用程序中的错误和异常。 ","date":"2026-04-06T18:10:14+08:00","image":"https://cdn.jsdelivr.net/gh/luckycloveryh/picgo-bed@main/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260524195218_522_12.jpg","permalink":"https://luckycloveryh.github.io/fuyou/p/k8s-monitoring-metrics-prometheus/","title":"19-监控"},{"content":"\nHeadlamp 介绍 Kubernetes 官方提供的用户管理项目是 Dashboard，这个项目在 2026 年就不再维护了，官方推荐使用 Headlamp 来代替 Dashboard 项目。\nHeadlamp 是一款易于使用且可扩展的 Kubernetes 用户界面， 包含 Kubernetes Dashboard 的功能集（即列出和查看资源），并提供额外的扩展功能。\nHeadlamp 特性：\n支持集群内操作或本地桌面应用模式 多集群管理 通过插件实现功能扩展 与 RBAC 关联的操作权限控制（无权限时禁止删除/更新） 可撤销的创建/更新/删除操作 带文档支持的日志、执行和资源编辑器 读写/交互模式（操作基于权限控制） Headlamp 部署 Headlamp 支持两种部署方式 ：\n集群内部署 使用桌面应用 接下来我们演示一下这两种部署方式：\n集群内部署 集群内部署就是把 Headlamp 部署到 Kubernetes 集群中，再将 Headlamp 暴露到集群外部，我们就可以通过 web 界面管理集群了，集群内部署使用 Helm 工具即可。\n第一步：添加 chart 仓库\n1 2 3 4 5 6 7 8 9 10 11 12 13 # 添加仓库 helm repo add headlamp https://kubernetes-sigs.github.io/headlamp/ # 搜索仓库中 chart 包 $ helm search repo headlamp NAME CHART VERSION APP VERSION DESCRIPTION headlamp/headlamp 0.40.0 0.40.0 Headlamp is an easy-to-use and extensible Kuber... # 下载离线 chart 包 helm pull headlamp/headlamp --version=0.40.0 # 解压 chart 包 tar xvf headlamp-0.40.0.tgz 下载离线 chart 包的目的主要是方便查看 values.yaml 中提供了哪些可以自定义的参数\n第二步： 安装 headlamp\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 $ helm upgrade --install my-headlamp \\ --set service.type=NodePort \\ --namespace webui \\ --create-namespace \\ headlamp Release \u0026#34;my-headlamp\u0026#34; does not exist. Installing it now. NAME: my-headlamp LAST DEPLOYED: Tue Mar 3 16:51:53 2026 NAMESPACE: webui STATUS: deployed REVISION: 1 DESCRIPTION: Install complete TEST SUITE: None NOTES: 1. Get the application URL by running these commands: export NODE_PORT=$(kubectl get --namespace webui -o jsonpath=\u0026#34;{.spec.ports[0].nodePort}\u0026#34; services my-headlamp) export NODE_IP=$(kubectl get nodes --namespace webui -o jsonpath=\u0026#34;{.items[0].status.addresses[0].address}\u0026#34;) echo http://$NODE_IP:$NODE_PORT 2. Get the token using kubectl create token my-headlamp --namespace webui 第三步：访问 headlamp\n通过四层 Service 访问管理界面 1 2 3 $ kubectl -n webui get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE my-headlamp NodePort 10.106.59.169 \u0026lt;none\u0026gt; 80:30998/TCP 114s 使用任意节点 IP+30998 端口就可以看到 headlamp 登录界面 。\n通过 七层 Ingress 访问 修改 valumes.yaml 中配置，按如下修改， 改为自己的域名即可。这个配置文件可以为我们创建 ingress 规则， 不过要通过 ingress 访问应用，需要集群中配置好了 Ingress Controller，根据自己安装的 Ingress Controller修改 ingressClassName 的值。\n1 2 3 4 5 6 7 8 9 10 11 12 13 ingress: # -- Enable ingress controller resource enabled: true ingressClassName: \u0026#34;nginx\u0026#34; hosts: - host: ui.chijinjing.cn paths: - path: / type: Prefix tls: - secretName: headlamp-tls hosts: - ui.xxhf.cc Headlamp 需要使用 HTTPS 协议访问，ingress 规则中启动了 TLS，我们需要创建一个 TLS 类型的 Secret，保存证书和私钥。\n1 2 3 4 kubectl -n webui \\ create secret tls headlamp-tls \\ --cert=ui.xxhf.cc_bundle.crt \\ --key=ui.xxhf.cc.key 更新 headlamp Release，使配置生效。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $ helm upgrade --install my-headlamp --set service.type=NodePort --namespace webui --create-namespace headlamp Release \u0026#34;my-headlamp\u0026#34; has been upgraded. Happy Helming! NAME: my-headlamp LAST DEPLOYED: Tue Mar 3 17:03:13 2026 NAMESPACE: webui STATUS: deployed REVISION: 2 DESCRIPTION: Upgrade complete TEST SUITE: None NOTES: 1. Get the application URL by running these commands: https://ui.chijinjing.cn/ 2. Get the token using kubectl create token my-headlamp --namespace webui Release 更新后可以看到 ingress 规则创建成功了\n1 2 3 $ kubectl -n webui get ingress NAME CLASS HOSTS ADDRESS PORTS AGE my-headlamp nginx ui.chijinjing.cn 80, 443 3m17s 使用上面的域名访问 headlamp，可以看到如下登录界面 ：\n桌面应用部署 桌面应用是通过 kubeconfig 文件连接集群，也支持多集群的管理，还是比较方便的。\n第一步：下载应用\n主流的操作系统都支持\n我这里下载一个 windows 桌面应用： https://github.com/kubernetes-sigs/headlamp/releases/download/v0.40.1/Headlamp-0.40.1-win-x64.exe`` 第二步：安装\n安装成功后可以看到如下的界面：\n第三步： 添加 **kubeconfig **文件连接集群\n在 Kubernetes 集群中下载 kubeconfig 文件，需要确保和集群中的 kube-apiserver 网络是通的。\nHeadlamp 访问 集群内部署方式访问 Headlamp 需要使用 Account token ，使用下面的命令生成一个 token，这个 token 默认有效期是一个小时。\n1 2 3 4 5 6 kubectl create token my-headlamp -n webui kubectl get secrets cs-admin -o jsonpath=\u0026#39;{.data.token}\u0026#39; | base64 -d 登录成功后可以看到如下界面 ：\n管理功能比 Dashboard 项目丰富一些，使用体验还不错。\n","date":"2026-04-06T18:07:39+08:00","image":"https://cdn.jsdelivr.net/gh/luckycloveryh/picgo-bed@main/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260524195216_521_12.jpg","permalink":"https://luckycloveryh.github.io/fuyou/p/k8s-headlamp-web-ui/","title":"Headlamp"},{"content":"Helm 基础知识 Helm 目前是 Kubernetes 服务编排事实上的标准。Helm 提供了多种功能来支持 Kubernetes 的服务编排\nHelm 是什么 Helm 是 Kubernetes 的包管理器，类似于 Python 的 pip ，centos 的 yum 。Helm 主要用来管理 Chart 包。Helm Chart 包中包含一系列 YAML 格式的 Kubernetes 资源定义文件，以及这些资源的配置，可以通过 Helm Chart 包来整体维护这些资源。\nHelm 也提供了一个 helm 命令行工具，该工具可以基于 Chart 包一键创建应用，在创建应用时，可以自定义 Chart 配置。应用发布者可以通过 Helm 打包应用、管理应用依赖关系、管理应用版本，并发布应用到软件仓库；对于使用者来说，使用 Helm 后不需要编写复杂的应用部署文件，可以非常方便地在 Kubernetes 上查找、安装、升级、回滚、卸载应用程序。\nHelm 最新的版本是 v3，Helm3 以 Helm2 的核心功能为基础，对 Chart repo、发行版管理、安全性和 library Charts 进行了改进。和 Helm2 比起来，Helm3 最明显的变化是删除了 Tiller（Helm2 是一种 Client-Server 结构，客户端称为 Helm，服务器称为 Tiller）。Helm3 还新增了一些功能，并废弃或重构了 Helm2 的部分功能，与 Helm2 不再兼容。\nHelm3 架构图如下：\n上面的架构图中，核心是 Helm Client（helm命令）和 Helm Chart 包。helm 命令可以从 Chart Repository 中下载 Helm Chart 包，读取kubeconfig文件，并构建 kube-apiserver REST API 接口的 HTTP 请求。通过调用 Kubernetes 提供的 REST API 接口，将 Chart 包中包含的所有以 YAML 格式定义的 Kubernetes 资源，在 Kubernetes 集群中创建。\n这些资源以 Release 的形式存在于 Kubernetes 集群中，每个 Release 又包含多个 Kubernetes 资源，例如 Deployment、Pod、Service 等。\nHelm 三大基本概念 要学习和使用 Helm，一定要了解 Helm 中的三大基本概念，Helm 的所有操作基本都是围绕着这些概念来进行的。下面我们看一下 Helm 的三大基本概念。\nChart： 代表一个 Helm 包。它包含了在 Kubernetes 集群中运行应用程序、工具或服务所需的所有 YAML 格式的资源定义文件。 Repository（仓库）： 它是用来存放和共享 Helm Chart 的地方，类似于存放源码的 GitHub 的 Repository，以及存放镜像的 Docker 的 Repository。 Release：它是运行在 Kubernetes 集群中的 Chart 的实例。一个 Chart 通常可以在同一个集群中安装多次。每一次安装都会创建一个新的 Release。 在了解了上述这些概念以后，我们就可以这样来解释 Helm：\nHelm 安装 chart 到 Kubernetes 集群中，每次安装都会创建一个新的 release。\n为什么要使用 Helm 在 Helm 中，可以理解为主要包含两类文件：模板文件和配置文件。模板文件通常有多个，配置文件通常有一个。Helm 的模板文件基于text/template模板文件，提供了更加强大的模板渲染能力。Helm 可以将配置文件中的值渲染进模板文件中，最终生成一个可以部署的 Kubernetes YAML 格式的资源定义文件，如下图所示：\nHelm基本操作 安装Helm 1 2 3 4 5 6 wget https://get.helm.sh/helm-v3.12.3-linux-amd64.tar.gz tar -xvzf helm-v3.12.3-linux-amd64.tar.gz mv linux-amd64/helm /usr/bin/ helm version version.BuildInfo{Version:\u0026#34;v3.6.3\u0026#34;, GitCommit:\u0026#34;d506314abfb5d21419df8c7e7e68012379db2354\u0026#34;, GitTreeState:\u0026#34;clean\u0026#34;, GoVersion:\u0026#34;go1.16.5\u0026#34;} 如果执行helm version可以成功打印出 helm 命令的版本号，说明 Helm 安装成功。\nHelm 各版本安装包地址见 Helm Releases。\n安装完helm命令后，可以安装helm命令的自动补全脚本。假如你用的 shell 是bash，安装方法如下：\n1 2 3 helm completion bash \u0026gt; $HOME/.helm-completion.bash echo \u0026#39;source $HOME/.helm-completion.bash\u0026#39; \u0026gt;\u0026gt; ~/.bashrc bash Chart 说明 1 2 3 4 5 6 7 8 9 10 11 12 13 14 helm create my-chart |-- charts # 该目录保存其他依赖的 chart（子 chart） |-- Chart.yaml |-- templates # chart 配置模板，用于渲染最终的 Kubernetes YAML 文件 | |-- deployment.yaml # Kubernetes deployment 配置 | |-- _helpers.tpl # 用于创建模板时的帮助类 | |-- hpa.yaml # Kubernetes hpa 配置 | |-- ingress.yaml # Kubernetes ingress 配置 | |-- NOTES.txt # 用户运行 helm install 时候的提示信息 | |-- serviceaccount.yaml # Kubernetes serviceaccount 配置 | |-- service.yaml # Kubernetes service 配置 | `-- tests | `-- test-connection.yaml `-- values.yaml # 定义 chart 模板中的自定义配置的默认值，可以在执行 helm install 或 helm update 的时候覆盖 以上是 helm 为我们自动创建的目录结构，我们还可以在 templates 目录加其他 Kubernetes 对象的配置，比如 ConfigMap、DaemonSet 等。\n我们查看下使用 helm create 命令自动生成的 templates/service.yaml 文件。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 apiVersion: v1 kind: Service metadata: name: {{ include \u0026#34;mychart.fullname\u0026#34; . }} labels: {{- include \u0026#34;mychart.labels\u0026#34; . | nindent 4 }} spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} targetPort: http protocol: TCP name: http selector: {{- include \u0026#34;mychart.selectorLabels\u0026#34; . | nindent 4 }} 可以看到其中有很多{{ }} 包围的字段，这是使用的 Go template 创建的自定义字段，其中 mychart 开头的都是在 _helpers.tpl 中生成的定义。\n例如 _helpers.tpl 中对 chart.fullname 的定义：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 {{/* Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). If release name contains chart name it will be used as a full name. */}} {{- define \u0026#34;mychart.fullname\u0026#34; -}} {{- if .Values.fullnameOverride }} {{- .Values.fullnameOverride | trunc 63 | trimSuffix \u0026#34;-\u0026#34; }} {{- else }} {{- $name := default .Chart.Name .Values.nameOverride }} {{- if contains $name .Release.Name }} {{- .Release.Name | trunc 63 | trimSuffix \u0026#34;-\u0026#34; }} {{- else }} {{- printf \u0026#34;%s-%s\u0026#34; .Release.Name $name | trunc 63 | trimSuffix \u0026#34;-\u0026#34; }} {{- end }} {{- end }} {{- end }} 我们再看下 values.yaml 文件中有这样的一段配置：\n1 2 3 service: type: ClusterIP port: 80 在使用 helm install 或 helm update 时，会渲染 templates/service.yaml 文件中的 {{ .Values.service.type }} 和 {{ .Values.service.port }} 的值。\nHelm 部署应用 初始化仓库 安装完 Helm 之后，就可以使用 helm 命令添加一个 Chart 仓库。类似于用来托管 Docker 镜像的 DockerHub、用来托管代码的 GitHub，Chart 包也有一个托管平台，当前比较流行的 Chart 包托管平台是 Artifact Hub。\nArtifact Hub 上有很多 Chart 仓库，我们可以添加需要的 Chart 仓库，这里我们添加 BitNami 提供的 Chart 仓库：\n1 2 helm repo add bitnami https://charts.bitnami.com/bitnami # 添加 Chart Repository helm repo list # 查看添加的 Repository 列表 添加完成后，我们可以通过helm search命令，来查询需要的 Chart 包。helm search支持两种不同的查询方式，这里我来介绍下。\nhelm search repo ：从你使用 helm repo add 添加到本地 Helm 客户端中的仓库里查找。该命令基于本地数据进行搜索，无需连接外网。 helm search hub ：从 Artifact Hub 中查找并列出 Helm Charts。 Artifact Hub 中存放了大量的仓库。 Helm 搜索使用模糊字符串匹配算法，所以你可以只输入名字的一部分。下面是一个helm search的示例：\n1 2 3 4 5 6 7 8 9 10 [root@node-01 18-helm]# helm search repo bitnami NAME CHART VERSION APP VERSION DESCRIPTION bitnami/airflow 16.1.6 2.7.3 Apache Airflow is a tool to express and execute... bitnami/apache 10.2.4 2.4.58 Apache HTTP Server is an open-source HTTP serve... bitnami/apisix 2.2.7 3.7.0 Apache APISIX is high-performance, real-time AP... bitnami/appsmith 2.1.8 1.9.49 Appsmith is an open source platform for buildin... bitnami/argo-cd 5.2.9 2.9.2 Argo CD is a continuous delivery tool for Kuber... bitnami/argo-workflows 6.1.4 3.5.1 Argo Workflows is meant to orchestrate Kubernet... bitnami/aspnet-core 5.0.1 8.0.0 ASP.NET Core is an open-source framework for we... # ... and many more 安装一个 Chart 查询到自己需要的 Helm Chart 后，就可以通过 helm install 命令来安装一个 Chart。helm install 支持从多种源进行安装：\nChart 的 Repository。 helm install bitnami/nginx --generate-name 本地的 Chart Archive，例如 helm install foo foo-1.0.0.tgz。 一个未打包的 Chart 路径，例如 helm install foo path/to/foo。 一个完整的 URL，例如 helm install foo ``https://example.com/charts/foo-1.0.0.tgz。 这里，我们选择通过 bitnami/nginx Chart 包来安装一个 Nginx应用。你可以执行 helm show chart bitnami/nginx 命令，来简单了解这个 Chart 的基本信息。 或者，你也可以执行 helm show all bitnami/nginx，获取关于该 Chart 的所有信息。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 [root@node-01 18-helm]# helm repo update Hang tight while we grab the latest from your chart repositories... ...Successfully got an update from the \u0026#34;bitnami\u0026#34; chart repository Update Complete. ⎈Happy Helming!⎈ [root@node-01 18-helm]# [root@node-01 18-helm]# helm install bitnami/nginx --generate-name NAME: nginx-1703602225 LAST DEPLOYED: Tue Dec 26 22:50:28 2023 NAMESPACE: default STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: CHART NAME: nginx CHART VERSION: 15.5.1 APP VERSION: 1.25.3 ** Please be patient while the chart is being deployed ** NGINX can be accessed through the following DNS name from within your cluster: nginx-1703602225.default.svc.cluster.local (port 80) To access NGINX from outside the cluster, follow the steps below: 1. Get the NGINX URL by running these commands: NOTE: It may take a few minutes for the LoadBalancer IP to be available. Watch the status with: \u0026#39;kubectl get svc --namespace default -w nginx-1703602225\u0026#39; export SERVICE_PORT=$(kubectl get --namespace default -o jsonpath=\u0026#34;{.spec.ports[0].port}\u0026#34; services nginx-1703602225) export SERVICE_IP=$(kubectl get svc --namespace default nginx-1703602225 -o jsonpath=\u0026#39;{.status.loadBalancer.ingress[0].ip}\u0026#39;) echo \u0026#34;http://${SERVICE_IP}:${SERVICE_PORT}\u0026#34; 在上面的例子中，我们通过安装bitnami/nginx这个 Chart，创建了一个 nginx-1703602225-7b466f68f9-ljzx2 Release。 \u0026ndash;generate-name参数告诉 Helm 自动为这个 Release 命名。\n在安装过程中，Helm 客户端会打印一些有用的信息，包括哪些资源已经被创建，Release 当前的状态，以及你是否还需要执行额外的配置步骤。\n安装完之后，你可以使用 helm status 来追踪 Release 的状态。\n1 2 3 4 5 [root@node-01 18-helm]# helm status nginx-1703602225 NAME: nginx-1703602225 LAST DEPLOYED: Tue Dec 26 22:50:28 2023 NAMESPACE: default STATUS: deployed 每当你执行 helm install 的时候，都会创建一个新的发布版本。所以一个 Chart 在同一个集群里面可以被安装多次，每一个都可以被独立地管理和升级。\nhelm install命令会将 templates 渲染成最终的 Kubernetes 能够识别的 YAML 格式，然后安装到 Kubernetes 集群中。\n自定义 Chart 上面的安装方式只会使用 Chart 的默认配置选项，很多时候我们需要自定义 Chart 来指定我们想要的配置。使用 helm show values 可以查看 Chart 中的可配置选项：\n1 2 3 4 5 6 7 8 [root@node-01 18-helm]# helm show values bitnami/nginx global: imageRegistry: \u0026#34;\u0026#34; ## E.g. ## imagePullSecrets: ## - myRegistryKeySecretName ## imagePullSecrets: [] 然后，你可以使用 YAML 格式的文件，覆盖上述任意配置项，并在安装过程中使用该文件。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 nginx-values.yaml replicaCount: 2 revisionHistoryLimit: 20 updateStrategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 [root@node-01 18-helm]# helm install nginx-01 -f nginx-values.yaml nginx-15.4.3.tgz NAME: nginx-01 LAST DEPLOYED: Tue Dec 26 23:10:53 2023 NAMESPACE: default STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: CHART NAME: nginx CHART VERSION: 15.4.3 APP VERSION: 1.25.3 上述命令将为 Nginx 修改副本数为2，revision的历史记录保存20条，修改了滚动更新的策略。Chart 中的其他默认配置保持不变。\n安装过程中，有两种传递配置数据的方式。\n-f，--values：使用 YAML 文件覆盖配置。可以指定多次，优先使用最右边的文件。 --set：通过命令行的方式对指定配置项进行覆盖。 如果同时使用两种方式，则 --set 中的值会被合并到 --values 中，但是 --set 中的值优先级更高。在--set中覆盖的内容会被保存在 ConfigMap 中。你可以通过 helm get values 来查看指定 Release 中\n--set 设置的值。\n1 2 3 4 5 6 7 8 9 [root@node-01 18-helm]# helm get values nginx-01 USER-SUPPLIED VALUES: replicaCount: 2 revisionHistoryLimit: 20 updateStrategy: rollingUpdate: maxSurge: 1 maxUnavailable: 0 type: RollingUpdate 我们看一下 --set 的格式和限制\n\u0026ndash;set 选项使用0或多个 key-value 对。最简单的用法类似于\u0026ndash;set name=value，等价于下面这个 YAML 格式：\n1 name: value 多个值之间使用逗号分割，因此\u0026ndash;set a=b,c=d 的 YAML 表示是：\n1 2 a: b c: d \u0026ndash;set还支持更复杂的表达式。例如，\u0026ndash;set outer.inner=value 被转换成了：\n1 2 outer: inner: value 列表使用花括号{}来表示。例如，\u0026ndash;set name={a, b, c} 被转换成了：\n1 2 3 4 name: - a - b - c 查看 Release 通过helm list可以查看当前集群、当前 Namespace 下安装的 Release 列表：\n1 2 3 4 [root@node-01 18-helm]# helm ls NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION nginx-01 default 1 2023-12-26 23:10:53.412289417 +0800 CST deployed nginx-15.4.3 1.25.3 nginx-1703602225 default 1 2023-12-26 22:50:28.610489775 +0800 CST deployed nginx-15.5.1 1.25.3 可以看到，我们创建了两个 Release，这些 Release 位于 default 命名空间中。上述命令，也列出了 Release 的更新时间、状态、Chart 的版本等。\n升级 Release 部署完应用之后，后续还可能升级应用，可以通过helm upgrade命令来升级应用。升级操作会基于已有的 Release，根据提供的信息进行升级。Helm 在更新时，只会变更有更改的内容。\n例如，这里我们升级 nginx-01 ，变更它的 Root 密码：\n1 2 3 4 5 6 7 [root@node-01 18-helm]# helm upgrade nginx-01 bitnami/nginx --set containerPorts.http=\u0026#39;80\u0026#39; Release \u0026#34;nginx-01\u0026#34; has been upgraded. Happy Helming! NAME: nginx-01 LAST DEPLOYED: Tue Dec 26 23:23:29 2023 NAMESPACE: default STATUS: deployed REVISION: 2 在上面的例子中，nginx-01 这个 Release 使用相同的 Chart 进行升级，但更新了 容器监听的端口号。\n我们可以使用 helm get values 命令，来看看配置值是否真的生效了：\n1 2 3 4 [root@node-01 18-helm]# helm get values nginx-01 USER-SUPPLIED VALUES: containerPorts: http: 80 可以看到 containerPorts.http 的新值已经被部署到集群中了。\n假如发布失败，我们也很容易通过 helm rollback [RELEASE] [REVISION] 命令，回滚到之前的发布版本。\n1 2 [root@node-01 18-helm]# helm rollback nginx-01 Rollback was a success! Happy Helming! 上面这条命令将我们的 nginx-01 回滚到了它最初的版本。Release 版本其实是一个增量修订（revision）。 每当发生了一次安装、升级或回滚操作，revision 的值就会加1。第一次 revision 的值永远是1。\n我们可以使用 helm history [RELEASE] 命令来查看一个特定 Release 的修订版本号：\n1 2 3 4 5 [root@node-01 18-helm]# helm history nginx-01 REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION 1 Tue Dec 26 23:10:53 2023 superseded nginx-15.4.3 1.25.3 Install complete 2 Tue Dec 26 23:23:29 2023 superseded nginx-15.5.1 1.25.3 Upgrade complete 3 Tue Dec 26 23:27:41 2023 deployed nginx-15.4.3 1.25.3 Rollback to 1 卸载 Release 你可以使用helm uninstall命令卸载一个 Release：\n1 2 [root@node-01 18-helm]# helm uninstall nginx-01 release \u0026#34;nginx-01\u0026#34; uninstalled 上述命令会从 Kubernetes 卸载 nginx-01， 它将删除和该版本关联的所有资源（Service、Deployment、Pod、ConfigMap 等），包括该 Release 的所有版本历史。\nHelm 命令 Helm 常用命令如下：\nhelm create：在本地创建新的 chart； helm intall：安装 chart； helm list：列出所有 release； helm repo：列出、增加、更新、删除 chart 仓库； helm rollback：回滚 release 到历史版本； helm pull：拉取远程 chart 到本地； helm search：使用关键词搜索 chart； helm uninstall：卸载 release； helm upgrade：升级 release； 使用 helm -h 可以查看 Helm 命令行使用详情，也可以参考 Helm 文档。\nChartMuseum (Chart 仓库) 使用本地存储 Make sure you have read-write access to ./chartstorage (will create if doesn’t exist on first upload)\n1 2 3 4 5 6 7 ./chartmuseum --debug --port=8080 \\ --storage=\u0026#34;local\u0026#34; \\ --storage-local-rootdir=\u0026#34;./chartstorage\u0026#34; helm repo add my-repo http://127.0.0.1:8080 helm plugin install https://github.com/chartmuseum/helm-push helm cm-push nginx-15.4.3.tgz my-repo ChartMuseum 官网\n上传 chart 包到 仓库 1 2 3 4 5 6 7 8 9 10 11 12 13 # 下载 插件 wget https://github.com/chartmuseum/helm-push/releases/download/v0.10.4/helm-push_0.10.4_linux_amd64.tar.gz mkdir /root/.local/share/helm/plugins/helm-push -p tar xvf helm-push_0.10.4_linux_amd64.tar.gz -C /root/.local/share/helm/plugins/helm-push # 上传 chart 包 helm cm-push nginx-15.4.3.tgz my-repo # 更新仓库 helm repo update my-repo [root@node-01 18-helm]# helm search repo my-repo NAME CHART VERSION APP VERSION DESCRIPTION my-repo/nginx 15.4.3 1.25.3 NGINX Open Source is a web server that can be a... 附录 镜像 1 registry.cn-beijing.aliyuncs.com/xxhf/nginx:1.27.0-debian-12-r5 ","date":"2026-04-06T18:04:16+08:00","image":"https://cdn.jsdelivr.net/gh/luckycloveryh/picgo-bed@main/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260524195229_528_12.jpg","permalink":"https://luckycloveryh.github.io/fuyou/p/k8s-helm-package-manager/","title":"18 - Helm"},{"content":"认证 Kubernetes API Server 组件是 Kubernetes 集群资源操作的唯一入口，它通过 HTTP RESTful 的形式暴露服务，允许不同的用户、外部组件等访问它。我们使用 curl 命令去模拟访问 apisever 请求过程中，发生了什么。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 [root@node-01 ~]# curl https://192.168.11.56:6443/api/v1/namespaces -k { \u0026#34;kind\u0026#34;: \u0026#34;Status\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;metadata\u0026#34;: {}, \u0026#34;status\u0026#34;: \u0026#34;Failure\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;namespaces is forbidden: Us er \\\u0026#34;system:anonymous\\\u0026#34; cannot list resource \\\u0026#34;namespaces\\\u0026#34; in API group \\\u0026#34;\\\u0026#34; at the cluster scope\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;Forbidden\u0026#34;, \u0026#34;details\u0026#34;: { \u0026#34;kind\u0026#34;: \u0026#34;namespaces\u0026#34; }, \u0026#34;code\u0026#34;: 403 } 从上面返回的结果可以看出：\napiserver 识别这次请求的用户为 system:anonymous apiserver 禁止了该用户 list namespaces，并返回 403 所以通过上面的测试大致了解 apiserver 的工作机制：\n首先，它会识别请求的用户是谁（AuthN） 然后，它会识别该用户具有什么样的权限（AuthZ） 认证插件\n鉴权 RBAC 权限控制 在 Kubernetes 中资源对象的操作都是通过 kube-apiserver 进行的，那么集群是怎样知道我们的请求就是合法的请求呢？这个就需要了解 Kubernetes 中另外一个非常重要的知识点了：RBAC（基于角色的权限控制）。\n管理员可以通过 Kubernetes API 动态配置策略来启用RBAC，需要在 kube-apiserver 中添加参数--authorization-mode=RBAC，如果使用的kubeadm 安装的集群那么是默认开启了 RBAC 的，可以通过查看 Master 节点上 apiserver 的静态 Pod 定义文件：\n1 2 3 4 cat /etc/kubernetes/manifests/kube-apiserver.yaml ... - --authorization-mode=Node,RBAC ... API 对象 1 2 3 4 5 6 7 8 9 10 11 kubectl get --raw / { \u0026#34;paths\u0026#34;: [ \u0026#34;/api\u0026#34;, \u0026#34;/api/v1\u0026#34;, \u0026#34;/apis\u0026#34;, \u0026#34;/apis/\u0026#34;, ...... \u0026#34;/version\u0026#34; ] } 比如我们来查看批处理这个操作，在我们当前这个版本中存在两个版本的操作：/apis/batch/v1 和 /apis/batch/v1beta1，分别暴露了可以查询和操作的不同实体集合，同样我们还是可以通过 kubectl 来查询对应对象下面的数据：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 $ kubectl get --raw /apis/batch/v1 | python -m json.tool { \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;groupVersion\u0026#34;: \u0026#34;batch/v1\u0026#34;, \u0026#34;kind\u0026#34;: \u0026#34;APIResourceList\u0026#34;, \u0026#34;resources\u0026#34;: [ { \u0026#34;categories\u0026#34;: [ \u0026#34;all\u0026#34; ], \u0026#34;kind\u0026#34;: \u0026#34;Job\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;jobs\u0026#34;, \u0026#34;namespaced\u0026#34;: true, \u0026#34;singularName\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;storageVersionHash\u0026#34;: \u0026#34;mudhfqk/qZY=\u0026#34;, \u0026#34;verbs\u0026#34;: [ \u0026#34;create\u0026#34;, \u0026#34;delete\u0026#34;, \u0026#34;deletecollection\u0026#34;, \u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;patch\u0026#34;, \u0026#34;update\u0026#34;, \u0026#34;watch\u0026#34; ] }, { \u0026#34;kind\u0026#34;: \u0026#34;Job\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;jobs/status\u0026#34;, \u0026#34;namespaced\u0026#34;: true, \u0026#34;singularName\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;verbs\u0026#34;: [ \u0026#34;get\u0026#34;, \u0026#34;patch\u0026#34;, \u0026#34;update\u0026#34; ] } ] } 但是这个操作和我们平时操作 HTTP 服务的方式不太一样，这里我们可以通过 kubectl proxy 命令来开启对 apiserver 的访问：\n1 2 $ kubectl proxy Starting to serve on 127.0.0.1:8001 然后重新开启一个新的终端，我们可以通过如下方式来访问批处理的 API 服务：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 curl http://127.0.0.1:8001/apis/batch/v1 { \u0026#34;kind\u0026#34;: \u0026#34;APIResourceList\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;groupVersion\u0026#34;: \u0026#34;batch/v1\u0026#34;, \u0026#34;resources\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;jobs\u0026#34;, \u0026#34;singularName\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;namespaced\u0026#34;: true, \u0026#34;kind\u0026#34;: \u0026#34;Job\u0026#34;, \u0026#34;verbs\u0026#34;: [ \u0026#34;create\u0026#34;, \u0026#34;delete\u0026#34;, \u0026#34;deletecollection\u0026#34;, \u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;patch\u0026#34;, \u0026#34;update\u0026#34;, \u0026#34;watch\u0026#34; ], \u0026#34;categories\u0026#34;: [ \u0026#34;all\u0026#34; ], \u0026#34;storageVersionHash\u0026#34;: \u0026#34;mudhfqk/qZY=\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;jobs/status\u0026#34;, \u0026#34;singularName\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;namespaced\u0026#34;: true, \u0026#34;kind\u0026#34;: \u0026#34;Job\u0026#34;, \u0026#34;verbs\u0026#34;: [ \u0026#34;get\u0026#34;, \u0026#34;patch\u0026#34;, \u0026#34;update\u0026#34; ] } ] } 通常，Kubernetes API 支持通过标准 HTTP POST、PUT、DELETE 和 GET 在指定 PATH 路径上创建、更新、删除和检索操作，并使用 JSON 作为默认的数据交互格式。\n比如现在我们要创建一个 Deployment 对象，那么我们的 YAML 文件的声明就需要怎么写：\n1 2 apiVersion: apps/v1 kind: Deployment 其中 Deployment 就是这个 API 对象的资源类型（Resource），apps 就是它的组（Group），v1 就是它的版本（Version）。API Group、Version 和 资源就唯一定义了一个 HTTP 路径，然后在 kube-apiserver 端对这个 url 进行了监听，然后把对应的请求传递给了对应的控制器进行处理而已，当然在 Kuberentes 中的实现过程是非常复杂的。\nRBAC 上面我们介绍了 Kubernetes 所有资源对象都是模型化的 API 对象，允许执行 CRUD(Create、Read、Update、Delete) 操作(也就是我们常说的增、删、改、查操作)，比如下面的这些资源：\nPods ConfigMaps Deployments Nodes Secrets Namespaces \u0026hellip;\u0026hellip; 对于上面这些资源对象的可能存在的操作有：\ncreate get delete list update edit watch exec patch 这些资源和 API Group 进行关联，比如 Pods 属于 Core API Group，而 Deployements 属于 apps API Group，现在我们要在 Kubernetes 中通过 RBAC 来对资源进行权限管理，除了上面的这些资源和操作以外，我们还需要了解另外几个概念：\nRule：规则，规则是一组属于不同 API Group 资源上的一组操作的集合 Role 和 ClusterRole：角色和集群角色，这两个对象都包含上面的 Rules 元素，二者的区别在于，在 Role 中，定义的规则只适用于单个命名空间，也就是和 namespace 关联的，而 ClusterRole 是集群范围内的，因此定义的规则不受命名空间的约束。另外 Role 和 ClusterRole 在Kubernetes 中都被定义为集群内部的 API 资源，和我们前面学习过的 Pod、Deployment 这些对象类似，都是我们集群的资源对象，所以同样的可以使用 YAML 文件来描述，用 kubectl 工具来管理。 Subject：主题，对应集群中操作的对象，集群中定义了3种类型的主题资源： User Account：用户，这是有外部独立服务进行管理的，管理员进行私钥的分配，用户可以使用 KeyStone 或者 Goolge 帐号，甚至一个用户名和密码的文件列表也可以。对于用户的管理集群内部没有一个关联的资源对象，所以用户不能通过集群内部的 API 来进行管理。 Group：组，这是用来关联多个账户的，集群中有一些默认创建的组，比如 cluster-admin。 Service Account：服务帐号，通过 Kub ernetes API 来管理的一些用户帐号，和 namespace 进行关联的，适用于集群内部运行的应用程序，需要通过 API 来完成权限认证，所以在集群内部进行权限操作，我们都需要使用到 ServiceAccount，这也是我们这节课的重点 RoleBinding 和 ClusterRoleBinding：角色绑定和集群角色绑定，简单来说就是把声明的 Subject 和我们的 Role 进行绑定的过程（给某个用户绑定上操作的权限），二者的区别也是作用范围的区别：RoleBinding 只会影响到当前 namespace 下面的资源操作权限，而 ClusterRoleBinding 会影响到所有的 namespace。 接下来我们来通过几个简单的示例，来学习下在 Kubernetes 集群中如何使用 RBAC。\n只能访问某个 namespace 的 User Account 我们想要创建一个 User Account，只能访问 dev-project 这个命名空间，对应的用户信息如下所示：\n1 2 username: dev-user group: developer 创建用户凭证 Kubernetes 没有 User Account 的 API 对象，不过要创建一个用户帐号的话也是挺简单的，可以使用 OpenSSL 命令创建 X509 证书来创建一个 User。\n1 2 3 4 5 [root@node-01 dev-project]# openssl genrsa -out dev-user.key 2048 Generating RSA private key, 2048 bit long modulus ............+++ .....................+++ e is 65537 (0x10001) 使用我们刚刚创建的私钥创建一个证书签名请求文件：dev-user.csr，要注意需要确保在-subj参数中指定用户名和组(CN表示用户名 common name ，O表示组 orgnazation )：\n1 openssl req -new -key dev-user.key -out dev-user.csr -subj \u0026#34;/CN=dev-user/O=developer\u0026#34; 然后找到我们 Kubernetes 集群的 CA 证书，如果使用的是 kubeadm 安装的集群，CA 相关证书位于 /etc/kubernetes/pki/ 目录下面，我们会利用该目录下面的 ca.crt 和 ca.key两个文件来批准上面的证书请求。生成最终的证书文件，我们这里设置证书的有效期为 3650 天：\n1 2 3 4 [root@node-01 dev-project]# openssl x509 -req -in dev-user.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out dev-user.crt -days 3650 Signature ok subject=/CN=dev-user/O=developer Getting CA Private Key 现在查看我们当前文件夹下面是否生成了一个证书文件：\n1 2 [root@node-01 dev-project]# ls dev-user.crt dev-user.csr dev-user.key 可以验证我们的证书是不是CA签发的\n1 2 [root@node-01 dev-project]# openssl verify -CAfile /etc/kubernetes/pki/ca.crt dev-user.crt dev-user.crt: OK 现在我们可以使用刚刚创建的证书文件和私钥文件在集群中创建新的凭证和上下文(Context):\n1 2 [root@node-01 dev-project]# kubectl config set-credentials dev-user --client-certificate=dev-user.crt --client-key=dev-user.key [--embed-certs=true ] User \u0026#34;dev-user\u0026#34; set. 我们可以看到一个用户 dev-user 创建了，然后为这个用户设置新的 Context，我们这里指定特定的一个 namespace：\n1 2 [root@node-01 dev-project]# kubectl config set-context dev-user-context --cluster=kubernetes --namespace=dev-project --user=dev-user Context \u0026#34;dev-user-context\u0026#34; created. 到这里，我们的用户 dev-user 就已经创建成功了，现在我们使用当前的这个配置文件来操作 kubectl 命令的时候，应该会出现错误，因为我们还没有为该用户定义任何操作的权限呢：\n1 2 [root@node-01 dev-project]# kubectl get pods --context=dev-user-context Error from server (Forbidden): pods is forbidden: User \u0026#34;dev-user\u0026#34; cannot list resource \u0026#34;pods\u0026#34; in API group \u0026#34;\u0026#34; in the namespace \u0026#34;dev-project\u0026#34; 创建角色 用户创建完成后，接下来就需要给该用户添加操作权限，我们来定义一个 YAML 文件，创建一个允许用户操作 Deployment、Pod、ReplicaSets 的角色，如下定义：(dev-user-role.yaml)\n1 2 3 4 5 6 7 8 9 apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: dev-user-role namespace: dev-project rules: - apiGroups: [\u0026#34;\u0026#34;, \u0026#34;apps\u0026#34;] resources: [\u0026#34;deployments\u0026#34;, \u0026#34;replicasets\u0026#34;, \u0026#34;pods\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;watch\u0026#34;, \u0026#34;create\u0026#34;, \u0026#34;update\u0026#34;, \u0026#34;patch\u0026#34;, \u0026#34;delete\u0026#34;] # 也可以使用[\u0026#39;*\u0026#39;] 其中 Pod 属于 core 这个 API Group，在 YAML 中用空字符就可以，而 Deployment 和 ReplicaSet 现在都属于 apps 这个 API Group（如果不知道则可以用 kubectl explain 命令查看），所以 rules 下面的 apiGroups 就综合了这几个资源的 API Group：[\u0026quot;\u0026quot;, \u0026ldquo;apps\u0026rdquo;]，其中verbs 就是我们上面提到的可以对这些资源对象执行的操作，我们这里需要所有的操作方法，所以我们也可以使用[\u0026rsquo;*\u0026rsquo;]来代替。然后直接创建这个 Role：\n1 2 [root@node-01 useraccount]# kubectl apply -f dev-user-role.yaml role.rbac.authorization.k8s.io/dev-user-role created 创建角色权限绑定 Role 创建完成了，但是很明显现在我们这个 Role 和我们的用户 dev-user 还没有任何关系，所以我们就需要创建一个 RoleBinding 对象，在 dev-project 这个命名空间下面将上面的 dev-user-role 角色和用户 dev-user 进行绑定：（dev-user-rolebinding.yaml）\n1 2 3 4 5 6 7 8 9 10 11 12 13 apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: dev-user-rolebinding namespace: dev-project subjects: - kind: User name: dev-user apiGroup: \u0026#34;\u0026#34; roleRef: kind: Role name: dev-user-role apiGroup: rbac.authorization.k8s.io 上面的 YAML 文件中我们看到了 subjects 字段，就是我们上面提到的用来操作集群的对象，对应上面的 User 帐号 dev-user，使用 kubectl 创建上面的资源对象：\n1 2 [root@node-01 useraccount]# kubectl apply -f dev-user-rolebinding.yaml rolebinding.rbac.authorization.k8s.io/dev-user-rolebinding created 验证 现在我们可以使用 dev-user-context 上下文来操作集群了：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 [root@node-01 17-security]# kubectl -n dev-project apply -f nginx-dep.yaml --context=dev-user-context deployment.apps/nginx-dep created [root@node-01 17-security]# kubectl -n dev-project get pods --context=dev-user-context NAME READY STATUS RESTARTS AGE nginx-dep-79f77dd7b7-gbbb9 1/1 Running 0 50s nginx-dep-79f77dd7b7-p4bln 1/1 Running 0 50s [root@node-01 17-security]# kubectl -n dev-project get deployment,rs --context=dev-user-context NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/nginx-dep 2/2 2 2 2m32s NAME DESIRED CURRENT READY AGE replicaset.apps/nginx-dep-79f77dd7b7 2 2 2 2m32s 如果去获取其他的资源对象呢，可以看到并没有权限获取，因为我们没有为当前操作用户指定其他对象资源的访问权限，是符合我们的预期的。这样我们就创建了一个只有单个命名空间访问权限的普通 User 。\n1 2 3 4 5 [root@node-01 17-security]# kubectl -n dev-project get cm --context=dev-user-context Error from server (Forbidden): configmaps is forbidden: User \u0026#34;dev-user\u0026#34; cannot list resource \u0026#34;configmaps\u0026#34; in API group \u0026#34;\u0026#34; in the namespace \u0026#34;dev-project\u0026#34; [root@node-01 17-security]# [root@node-01 17-security]# kubectl -n default get cm --context=dev-user-context Error from server (Forbidden): configmaps is forbidden: User \u0026#34;dev-user\u0026#34; cannot list resource \u0026#34;configmaps\u0026#34; in API group \u0026#34;\u0026#34; in the namespace \u0026#34;default\u0026#34; 只能访问某个 namespace 的 ServiceAccount 上面我们创建了一个只能访问某个命名空间下面的 User Account，我们前面也提到过 subjects 下面还有一种类型的主题资源：ServiceAccount，现在我们来创建一个集群内部的用户只能操作 default 这个命名空间下面的 pods 、deployments 和 replicasets 对象，首先来创建一个 ServiceAccount 对象：\n1 kubectl -n default create sa ns-user 我们也可以定义成 YAML 文件的形式来创建：\n1 2 3 4 5 apiVersion: v1 kind: ServiceAccount metadata: name: ns-user namespace: default 然后新建一个 Role 对象：(ns-user-role.yaml)\n1 2 3 4 5 6 7 8 9 10 11 12 13 kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: namespace: default name: ns-user-role rules: - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;pods\u0026#34;, \u0026#34;pods/log\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;] - apiGroups: [\u0026#34;\u0026#34;, \u0026#34;apps\u0026#34;] resources: [\u0026#34;deployments\u0026#34;, \u0026#34;replicasets\u0026#34;, \u0026#34;pods\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;watch\u0026#34;, \u0026#34;create\u0026#34;, \u0026#34;update\u0026#34;, \u0026#34;patch\u0026#34;, \u0026#34;delete\u0026#34;] kubectl apply -f ns-user-role.yaml 然后创建一个 RoleBinding 对象，将上面的 namespace-user 和角色 namespace-user-role 进行绑定：(namespace-user-rolebinding.yaml)\n1 2 3 4 5 6 7 8 9 10 11 12 kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: namespace-user-rolebinding namespace: default subjects: - kind: ServiceAccount name: ns-user roleRef: kind: Role name: ns-user-role apiGroup: rbac.authorization.k8s.io 验证 1 2 3 4 5 6 7 8 9 [root@node-01 .kube]# kubectl --kubeconfig=ns-user-conf get pods NAME READY STATUS RESTARTS AGE nginx-dep-587b6fd57c-dwn9g 1/1 Running 0 4d9h redis-pv-sts-0 1/1 Running 0 4d6h redis-pv-sts-1 1/1 Running 0 4d6h [root@node-01 .kube]# [root@node-01 .kube]# [root@node-01 .kube]# kubectl --kubeconfig=ns-user-conf get cm Error from server (Forbidden): configmaps is forbidden: User \u0026#34;system:serviceaccount:default:namespace-user\u0026#34; cannot list resource \u0026#34;configmaps\u0026#34; in API group \u0026#34;\u0026#34; in the namespace \u0026#34;default\u0026#34; 可以全局访问的 ServiceAccount 刚刚我们创建的ServiceAccount 是和一个 Role 角色进行绑定的，如果我们创建一个新的 ServiceAccount，需要他操作的权限作用于所有的 namespace，这个时候我们就需要使用到 ClusterRole 和 ClusterRoleBinding 这两种资源对象了。同样，首先新建一个 ServiceAcount 对象：(cs-admin.yaml)\n1 2 3 4 5 apiVersion: v1 kind: ServiceAccount metadata: name: cs-admin namespace: default 然后创建一个 ClusterRoleBinding 对象（cnych-clusterolebinding.yaml）:\n1 2 3 4 5 6 7 8 9 10 11 12 apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: cs-admin-clusterrolebinding subjects: - kind: ServiceAccount name: cs-admin namespace: default roleRef: kind: ClusterRole name: cluster-admin apiGroup: rbac.authorization.k8s.io 验证\n1 2 3 4 5 6 7 8 9 10 11 12 [root@node-01 .kube]# kubectl --kubeconfig=cs-admin-conf -n kube-system get pods NAME READY STATUS RESTARTS AGE calico-kube-controllers-7b8458594b-nr8dz 1/1 Running 0 53d calico-node-9w8tj 1/1 Running 0 53d coredns-6d8c4cb4d-phsh2 1/1 Running 0 53d coredns-6d8c4cb4d-zr8fx 1/1 Running 0 53d etcd-node 1/1 Running 0 53d kube-apiserver-node 1/1 Running 1 (24h ago) 6d10h kube-controller-manager-node 1/1 Running 3 (24h ago) 53d kube-proxy-pvsst 1/1 Running 0 33d kube-scheduler-node 1/1 Running 3 (24h ago) 53d nfs-client-provisioner-f95957677-jht7s 1/1 Running 3 (24h ago) 4d10h What can i do 1 2 # kubectl auth can-i get pods yes 准入 1 2 3 4 5 6 7 8 9 10 11 12 13 apiVersion: v1 kind: ResourceQuota metadata: name: object-counts namespace: default spec: hard: configmaps: \u0026#34;1\u0026#34; [root@node-01 admission]# kubectl apply -f quota-cm.yaml resourcequota/object-counts created [root@node-01 admission]# kubectl create cm cm-demo --from-literal=a=b error: failed to create configmap: configmaps \u0026#34;cm-demo\u0026#34; is forbidden: exceeded quota: object-counts, requested: configmaps=1, used: configmaps=8, limited: configmaps=1 附录 Kubeconfig 默认情况下，kubectl 在 $HOME/.kube 目录下查找名为 config 的文件。 你可以通过设置 KUBECONFIG 环境变量或者设置 --kubeconfig参数来指定其他 kubeconfig 文件。\n配置上下文 列出当前配置的 context\n1 2 3 4 [root@node-01 .kube]# kubectl config get-contexts CURRENT NAME CLUSTER AUTHINFO NAMESPACE dev-user-context kubernetes dev-user dev-project * kubernetes-admin@kubernetes kubernetes kubernetes-admin 切换上下文\n1 2 3 4 5 6 7 8 9 10 [root@node-01 .kube]# kubectl config use-context dev-user-context Switched to context \u0026#34;dev-user-context\u0026#34;. [root@node-01 .kube]# [root@node-01 .kube]# kubectl config get-contexts CURRENT NAME CLUSTER AUTHINFO NAMESPACE * dev-user-context kubernetes dev-user dev-project kubernetes-admin@kubernetes kubernetes kubernetes-admin [root@node-01 .kube]# [root@node-01 .kube]# kubectl -n kube-system get pods Error from server (Forbidden): pods is forbidden: User \u0026#34;dev-user\u0026#34; cannot list resource \u0026#34;pods\u0026#34; in API group \u0026#34;\u0026#34; in the namespace \u0026#34;kube-system\u0026#34; 指定 \u0026ndash;kubeconfig 文件 1 2 3 4 5 [root@node-01 .kube]# kubectl --kubeconfig=/root/.kube/namespace-user-conf get pods NAME READY STATUS RESTARTS AGE nginx-dep-587b6fd57c-dwn9g 1/1 Running 0 4d10h redis-pv-sts-0 1/1 Running 0 4d6h redis-pv-sts-1 1/1 Running 0 4d6h 配置环境变量 1 export KUBECONFIG=config ","date":"2026-04-06T18:02:14+08:00","image":"https://cdn.jsdelivr.net/gh/luckycloveryh/picgo-bed@main/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260524195228_527_12.jpg","permalink":"https://luckycloveryh.github.io/fuyou/p/k8s-cluster-security/","title":"17-集群安全"},{"content":"高级调度 nodeSelector nodeSelector 提供了一种最简单的方法将 Pod 约束调度到具有特定标签的节点上，这个特性工作中经常会用到，现在需要部署 Redis 或MySQL，把这些应用调度到有 SSD 磁盘的节点上。\nRedis 的 YAML 文件如下\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 apiVersion: apps/v1 kind: Deployment metadata: name: redis-cache spec: selector: matchLabels: app: redis replicas: 1 template: metadata: labels: app: redis spec: containers: - name: redis-server image: redis:5-alpine nodeSelector: disktype: ssd 在 YAML 中添加以下内容\n1 2 nodeSelector: disktype: ssd 调度器在调度时会检查节点是否有如下的 Tag： disktype: ssd ，如果没有则调度失败，pod 会进入 Pending 状态\n1 2 3 [root@node1 ~]# kubectl get pods NAME READY STATUS RESTARTS AGE redis-cache-5b666fdcb9-8jhmb 0/1 Pending 0 3s 查看 pod 事件日志\n1 2 3 4 5 6 7 8 [root@node1 ~]# kubectl describe pods redis-cache-5b666fdcb9-8jhmb Node-Selectors: disktype=ssd Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s node.kubernetes.io/unreachable:NoExecute op=Exists for 300s Events: Type Reason Age From Message Warning FailedScheduling 3m13s default-scheduler 0/2 nodes are available: 2 node(s) didn\u0026#39;t match Pod\u0026#39;s node affinity/selector. preemption: 0/2 nodes are available: 2 Preemption is not helpful for scheduling. 给节点打标签\n1 kubectl label nodes node1 disktype=ssd 调度成功\n1 2 3 4 5 6 7 8 Events: Type Reason Age From Message Warning FailedScheduling 5m40s default-scheduler 0/2 nodes are available: 2 node(s) didn\u0026#39;t match Pod\u0026#39;s node affinity/selector. preemption: 0/2 nodes are available: 2 Preemption is not helpful for scheduling. Normal Scheduled 110s default-scheduler Successfully assigned default/redis-cache-5b666fdcb9-8jhmb to node1 Normal Pulling 110s kubelet Pulling image \u0026#34;redis:3.2-alpine\u0026#34; Normal Pulled 104s kubelet Successfully pulled image \u0026#34;redis:3.2-alpine\u0026#34; in 5.389457086s Normal Created 104s kubelet Created container redis-server Normal Started 104s kubelet Started container redis-server nodeAffinity 节点亲和性概念上类似于 nodeSelector， 可以根据节点上的标签来约束 Pod 可以调度到哪些节点上。 节点亲和性有两种：\nrequiredDuringSchedulingIgnoredDuringExecution： 调度器只有在规则被满足的时候才能执行调度。此功能类似于 nodeSelector， 但其语法表达能力更强。 preferredDuringSchedulingIgnoredDuringExecution： 调度器会尝试寻找满足对应规则的节点。如果找不到匹配的节点，调度器仍然会调度该 Pod。 支持的操作符：\n操作符 行为 In 标签值存在于提供的字符串集中 NotIn 标签值不包含在提供的字符串集中 Exists 对象上存在具有此键的标签 DoesNotExist 对象上不存在具有此键的标签 以下操作符只能与 nodeAffinity 一起使用。\n操作符 行为 Gt 提供的值将被解析为整数，并且该整数小于通过解析此选择算符命名的标签的值所得到的整数 Lt 提供的值将被解析为整数，并且该整数大于通过解析此选择算符命名的标签的值所得到的整数 required affinity（硬亲和） 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 apiVersion: apps/v1 kind: Deployment metadata: name: redis-cache spec: selector: matchLabels: app: redis replicas: 1 template: metadata: labels: app: redis spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: disktype operator: In values: - ssd containers: - name: redis-server image: redis:5-alpine nodeSelector: disktype: ssd 将pod 调度到具有 disktype: ssd 的节点上\npreferred affinity （软亲和） 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 apiVersion: apps/v1 kind: Deployment metadata: name: redis-cache spec: selector: matchLabels: app: redis replicas: 5 template: metadata: labels: app: redis spec: affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 1 preference: matchExpressions: - key: disktype operator: In values: - ssd containers: - name: redis-server image: redis:5-alpine resources: limits: memory: 1Gi cpu: 1 requests: memory: 256Mi cpu: 100m 优选 具有 disktype: ssd 的节点，如果找不到，其它节点也可以运行。\nnode2 没有 disktype: ssd 这个标签，也可以调度， 是在 node1 跑满了之后，次选的 node2\n1 2 3 4 5 6 7 [root@node1 ~]# kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES redis-cache-5c787dcc74-2mc5m 1/1 Running 0 58m 10.233.96.10 node2 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; redis-cache-5c787dcc74-q4cth 1/1 Running 0 60m 10.233.90.15 node1 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; redis-cache-5c787dcc74-tv7lw 1/1 Running 0 60m 10.233.90.14 node1 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; redis-cache-5c787dcc74-xhhkt 1/1 Running 0 58m 10.233.90.16 node1 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; redis-cache-5c787dcc74-xz6sg 1/1 Running 0 58m 10.233.90.17 node1 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; podAffinity Pod 间亲和性与反亲和性可以基于已经在节点上运行的 Pod 的标签来约束 Pod 可以调度到的节点，而不是基于节点上的标签。\nPod 间亲和性与反亲和性的规则格式为“如果 X 上已经运行了一个或多个满足规则 Y 的 Pod， 则这个 Pod 应该（或者在反亲和性的情况下不应该）运行在 X 上”。 这里的 X 可以是节点、机架、云提供商可用区或地理区域或类似的拓扑域， Y 则是 Kubernetes 尝试满足的规则。\npodAffinity（pod 亲和性） 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - redis topologyKey: \u0026#34;kubernetes.io/hostname\u0026#34; containers: - name: nginx image: nginx resources: limits: memory: 1Gi cpu: 1 requests: memory: 256Mi cpu: 100m podAntiAffinity（pod反亲和性） 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - redis topologyKey: \u0026#34;kubernetes.io/hostname\u0026#34; podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 10 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - nginx topologyKey: \u0026#34;kubernetes.io/hostname\u0026#34; containers: - name: nginx image: nginx resources: limits: memory: 1Gi cpu: 1 requests: memory: 256Mi cpu: 100m topologyKey 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 apiVersion: v1 kind: Pod metadata: name: with-pod-affinity spec: affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: security operator: In values: - S1 topologyKey: topology.kubernetes.io/zone podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: security operator: In values: - S2 topologyKey: topology.kubernetes.io/zone containers: - name: with-pod-affinity image: registry.k8s.io/pause:2.0 本示例定义了一条 Pod 亲和性规则和一条 Pod 反亲和性规则。Pod 亲和性规则配置为 requiredDuringSchedulingIgnoredDuringExecution，而 Pod 反亲和性配置为 preferredDuringSchedulingIgnoredDuringExecution。\n亲和性规则规定，只有节点属于特定的区域 且该区域中的其他 Pod 已打上 security=S1 标签时，调度器才可以将示例 Pod 调度到此节点上。 例如，如果我们有一个具有指定区域（称之为 \u0026ldquo;Zone V\u0026rdquo;）的集群，此区域由带有 topology.kubernetes.io/zone=V 标签的节点组成，那么只要 Zone V 内已经至少有一个 Pod 打了 security=S1 标签， 调度器就可以将此 Pod 调度到 Zone V 内的任何节点。相反，如果 Zone V 中没有带有 security=S1 标签的 Pod， 则调度器不会将示例 Pod 调度给该区域中的任何节点。\n反亲和性规则规定，如果节点属于特定的区域 且该区域中的其他 Pod 已打上 security=S2 标签，则调度器应尝试避免将 Pod 调度到此节点上。 例如，如果我们有一个具有指定区域（我们称之为 \u0026ldquo;Zone R\u0026rdquo;）的集群，此区域由带有 topology.kubernetes.io/zone=R 标签的节点组成，只要 Zone R 内已经至少有一个 Pod 打了 security=S2 标签， 调度器应避免将 Pod 分配给 Zone R 内的任何节点。相反，如果 Zone R 中没有带有 security=S2 标签的 Pod， 则反亲和性规则不会影响将 Pod 调度到 Zone R。\ntaint and toleration 污点（taint）使节点可以排斥特定的Pod。\n容忍度（toleration）是应用于Pod上的，允许调度器调度带有对应污点的Pod。\n污点和容忍度（Toleration）相互配合，可以用来避免 Pod 被分配到不合适的节点上。 每个节点上都可以应用一个或多个污点，这表示对于那些不能容忍这些污点的 Pod， 是不会被该节点接受的。\n为节点添加污点\n1 kubectl taint node worker-01 nodetype=gpu:NoSchedule 删除污点\n1 kubectl taint node worker-01 nodetype- 完全匹配 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx tolerations: - key: \u0026#34;nodetype\u0026#34; operator: \u0026#34;Equal\u0026#34; value: \u0026#34;gpu\u0026#34; effect: \u0026#34;NoSchedule\u0026#34; 匹配任意 taint value operator 为 Exists\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx tolerations: - key: \u0026#34;nodetype\u0026#34; operator: \u0026#34;Exists\u0026#34; value: \u0026#34;\u0026#34; effect: \u0026#34;NoSchedule\u0026#34; 匹配任意 taint effect 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx tolerations: - key: \u0026#34;nodetype\u0026#34; operator: \u0026#34;Equal\u0026#34; value: \u0026#34;gpu\u0026#34; effect: \u0026#34;\u0026#34; 默认master节点污点\n1 2 3 4 5 6 7 kubectl get nodes # 查看节点污点 kubectl describe nodes master-01 | grep -i taint kubectl taint node xxx-nodename node-role.kubernetes.io/master- #将 Master 也当作 Node 使用 kubectl taint node xxx-nodename node-role.kubernetes.io/master=\u0026#34;\u0026#34;:NoSchedule #将 Master 恢复成 Master Only 状态 Pod 拓扑分布约束\n","date":"2026-04-06T18:00:27+08:00","image":"https://cdn.jsdelivr.net/gh/luckycloveryh/picgo-bed@main/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260524195226_526_12.jpg","permalink":"https://luckycloveryh.github.io/fuyou/p/k8s-scheduling-strategies/","title":"16-调度"},{"content":"Kubernetes 网络基础 在 Kubernetes 的网络模型中，每台服务器上的容器有自己独立的 IP 段，各个服务器之间的容器可以根据目标容器的 IP 地址进行访问。\n为了实现这一目标，重点解决以下两点：\n各台服务器上的容器 IP 段不能重叠，所以需要有某种 IP 段分配机制，为各台服务器分配独立的 IP 段； 从某个 Pod 发出的流量到达其所在服务器时，服务器网络层应当具备根据目标 IP 地址，将流量转发到该 IP 所属 IP 段对应的目标服务器的能力 。 总结起来，实现 Kubernetes 的容器网络重点需要关注两方面 ： IP 地址分配 和 路由 。\n主机内组网 Kubemetes 经典的主机内组网模型是 veth pair+ bridge 的方式 。\n当 Kubemetes 调度 Pod 在某个节点上运行时，它会在该节点的 Linux 内核中为 Pod 创建 network namespace ，供 Pod 内所有运行的容器使用 。 从容器的角度看， Pod 是具有一个网络接口的物理机器， Pod 中的所有容器都会看到此网络接口 。 因此，每个容器通过 localhost 就能访问同一个 Pod 内的其他容器 。\nKubemetes 使用 veth pair 将容器与主机的网络协议栈连接起来 ，从而使数据包可以进出 Pod。 容器放在主机根 network namespace 中 veth pair 的一端连接到 Linux 网桥 ，可让同一节点上的各 Pod 之间相互通信 。\n跨节点组网 综上所述，我们可以对Kubernetes 网络做如下总结：\nKubernetes 网络基础原则：\n每个 Pod 都拥有一个独立的 IP 地址，而且假定所有 Pod 都在一个可以直接连通的、扁平的网络空间中，不管是否运行在同一 Node 上都可以通过 Pod 的 IP 来访问。 Kubernetes 中 Pod 的 IP 是最小粒度 IP。同一个 Pod 内所有的容器共享一个网络协议栈，该模型称为 IP-per-Pod 模型。 Pod 内部看到的 IP 地址和端口与外部保持一致。同一个 Pod 内的不同容器共享网络，可以通过 localhost 来访问对方的端口，类似同一个 VM 内的不同进程。 IP-per-Pod 模型从端口分配、域名解析、服务发现、负载均衡、应用配置等角度看，Pod 可以看作是一台独立的 VM 或物理机。 CNI CNI 即容器网络接口（ Container Network Interface ） 。\n在 CNI 标准中，网络插件是独立的可执行文件，被上层的容器管理平台调用 。 网络插件只有两件事情要做：把容器加入网络或把容器从网络中删除。\nKubernetes 使用 CNI 网络插件的工作流程 ：\nKubelet 调用 CRI 创建 pause 容器，生成对应的 network namespace; 调用网络 driver ; CNI driver 根据配置调用具体的 CNI 插件； CNI 插件给 pause 容器配置正确的网络， Pod 中的其他容器都是用 pause 容器的网络栈 。 接下来我们看一下两款流行的 CNI 插件的工作方式，可以看到跨节点的容器之间是如何通信的。\nFlannel Flannel 可以为容器提供跨节点网络服务，其模型为集群内所有容器使用一个网络，然后在每个主机上从该网络中划分一个子网 。 flannel 为主机上的容器创建网络时，从子网中划分一个 IP 给容器 。 根据 Kubernetes 的模型，为每个 Pod 提供一个 IP, flannel 的模型正好与之契合 。 而且 flannel 安装方便且简单易用 。\nFlannel 不同 backend 讲解 flannel 通过在每一个节点上启动一个叫 flanneld 的进程，负责每一个节点上的子网划分，并将相关的配置信息（如各个节点的子网网段 、外部 IP 等）保存到 etcd 中，而具体的网络包转发交给具体的 backend 实现 。\nflanneld 可以在启动时通过配置文件指定不同的 backend 进行网络通信，目前比较成熟的 backend 有 UDP 、VXLAN`` 和 Host ``Gateway 三种。\nUDP：早期版本的Flannel使用 UDP 封装完成报文的跨越主机转发，其安全性及性能略有不足。\nVXLAN：Linux 内核在在 2012 年底的 v3.7.0之后加入了 VXLAN 协议支持，因此新版本的 Flannel也从 UDP 转换为 VXLAN，VXLAN 本质上是一种 tunnel（隧道）协议，用来基于三层网络实现虚拟的二层网络，目前flannel 的网络模型已经是基于 VXLAN 的叠加(覆盖)网络。\nHost-GW：也就是 Host GateWay，通过在 node 节点上创建到达各目标容器地址的路由表而完成报文的转发，因此这种方式要求各 node 节点本身必须处于同一个局域网(二层网络)中，因此不适用于网络变动频繁或比较大型的网络环境，但是其性能较好。\n目前， VXLAN 是官方推荐的一种 backend 实现方式； Host Gateway 一般用于对网络性能要求比较高的场景， 但需要基础网络架构的支持 ； UDP 则用于测试及一些比较老的不支持 VXLAN 的 Linux 内核 。\nBackend: UDP 1 2 3 4 卸载calico 1. kubectl delete -f calico.yaml 2. mv /etc/cni/net.d/10-calico.conflist /tmp (所有的 节点 都执行 ) 3. reboot 修改 flannel 配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 net-conf.json: | { \u0026#34;Network\u0026#34;: \u0026#34;10.244.0.0/16\u0026#34;, \u0026#34;Backend\u0026#34;: { \u0026#34;Type\u0026#34;: \u0026#34;udp\u0026#34; } } securityContext: capabilities: add: - NET_ADMIN - NET_RAW privileged: true 部署 flannel 网络插件 1 kubectl apply -f kube-flannel-udp.yml 查看 flannel Pod 状态 验证当前运行模式 查看当前 node 主机 IP 地址范围 1 2 3 4 5 [root@master-01 ~]# cat /run/flannel/subnet.env FLANNEL_NETWORK=10.244.0.0/16 FLANNEL_SUBNET=10.244.0.1/24 FLANNEL_MTU=1472 FLANNEL_IPMASQ=true flannel0 接口 flanneld 进程启动后，通过 ip addr 命令可以发现节点中多了一个 叫 flannel0 的网络接口 ：\n通过 netstat -ulnp 命令可以看到此时 flanneld 进程监听在 UDP 8285 端口\n1 2 [root@master-01 ~]# netstat -nlup | grep flannel udp 0 0 172.16.66.30:8285 0.0.0.0:* 23257/flanneld cni 接口 cni0 网桥信息(master 节点)\n1 2 [root@master-01 ~]# cat /var/lib/cni/flannel/fb00bc676469faad8628ed2f9b3d4a6ade69a8cee1dbf1e256e877b0d770dcd1 {\u0026#34;cniVersion\u0026#34;:\u0026#34;0.3.1\u0026#34;,\u0026#34;hairpinMode\u0026#34;:true,\u0026#34;ipMasq\u0026#34;:false,\u0026#34;ipam\u0026#34;:{\u0026#34;ranges\u0026#34;:[[{\u0026#34;subnet\u0026#34;:\u0026#34;10.244.0.0/24\u0026#34;}]],\u0026#34;routes\u0026#34;:[{\u0026#34;dst\u0026#34;:\u0026#34;10.244.0.0/16\u0026#34;}],\u0026#34;type\u0026#34;:\u0026#34;host-local\u0026#34;},\u0026#34;isDefaultGateway\u0026#34;:true,\u0026#34;isGateway\u0026#34;:true,\u0026#34;mtu\u0026#34;:1472,\u0026#34;name\u0026#34;:\u0026#34;cbr0\u0026#34;,\u0026#34;type\u0026#34;:\u0026#34;bridge\u0026#34;} cni0 网桥信息(worker 节点)\n1 2 [root@worker-01 ~]# cat /var/lib/cni/flannel/88cf2ddc597944e7714b3b0d5dde6f3d5b271ff44430a287e16214b826be9c6d {\u0026#34;cniVersion\u0026#34;:\u0026#34;0.3.1\u0026#34;,\u0026#34;hairpinMode\u0026#34;:true,\u0026#34;ipMasq\u0026#34;:false,\u0026#34;ipam\u0026#34;:{\u0026#34;ranges\u0026#34;:[[{\u0026#34;subnet\u0026#34;:\u0026#34;10.244.1.0/24\u0026#34;}]],\u0026#34;routes\u0026#34;:[{\u0026#34;dst\u0026#34;:\u0026#34;10.244.0.0/16\u0026#34;}],\u0026#34;type\u0026#34;:\u0026#34;host-local\u0026#34;},\u0026#34;isDefaultGateway\u0026#34;:true,\u0026#34;isGateway\u0026#34;:true,\u0026#34;mtu\u0026#34;:1472,\u0026#34;name\u0026#34;:\u0026#34;cbr0\u0026#34;,\u0026#34;type\u0026#34;:\u0026#34;bridge\u0026#34;} 创建测试容器 1 2 3 4 5 [root@master-01 15-network]# kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx-master-85cb9fbd9-tm887 1/1 Running 0 10s 10.244.0.6 master-01 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; nginx-worker-8454fcf9bf-cr74k 1/1 Running 0 5s 10.244.1.4 worker-01 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; nginx-worker-8454fcf9bf-lkfnh 1/1 Running 0 5s 10.244.1.5 worker-01 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; Node Name node-ip node-mac pod-id Pod Name cni0 master-01 172.16.66.30 00:50:56:a4:99:71 10.244.0.6 A 10.244.0.1 worker-01 172.16.66.31 00:50:56:a4:08:39 10.244.1.4 B 10.244.1.1 worker-01 172.16.66.31 00:50:56:a4:08:39 10.244.1.5 C 10.244.1.1 flannel UDP 模式本机通信实践 我们来看宿主机 host 上的路由信息，如下所示：\n1 2 3 4 5 6 7 8 [root@worker-01 ~]# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 172.16.66.1 0.0.0.0 UG 100 0 0 ens192 10.244.0.0 0.0.0.0 255.255.0.0 U 0 0 0 flannel0 10.244.1.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0 172.16.66.0 0.0.0.0 255.255.255.0 U 100 0 0 ens192 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0 当容器 B 发送到 同一个 subnet 的容器 C 时，因为二者处于同一个子网，所以容器 B 和 C 位于同一个宿主机 host 上 ，而容器 B 和 C 也均桥接在 cni0 上 。\n1 2 3 4 [root@worker-01 ~]# brctl show bridge name bridge id STP enabled interfaces cni0 8000.4205bfb04106 no veth4aa9785b veth67b0672e 借助网桥 cni0 ，即可实现同一个主机上容器 B 和 C 的直接通信 。\nflannel UDP 模式跨主机通信实践 容器 A 发出 ICMP 请求报文，通过 IP 封装后的形式为 10.244.0.6 （源）→ 10.244.1.4 （ 目的 ）。 此时通过容器 A 内的路由表匹配到应该将 IP 包发送到网关 10.244.0.1 ( cni0 网桥）。 到达 cni0 的 IP 包目的地 IP 10.244.1.4 ，匹配到节点 A 上第一条路由规则（ 10.244.0. 0 ） ， 内核通过查本机路由表知道应该将 IP 包发送给 flannel0 接口 。 发送给 flannel0 接口的 IP 包将被 flanneld 进程接收， flanneld 进程接收 IP 包后在原有的基础上进行 UDP 封包， UDP 封包的形式为 172.16.66.30:8285 → 172.16.66.31:8285 。 flanneld 将封装好的 UDP 报文经 eth0 发出，从这里可以看出网络包在通过 eth0 发出前先是加上了 UDP 头（ 8 个字节），再加上 IP 头（ 20 个字节）进行封装，这也是 flannel0 的 MTU 要比 eth0 的 MTU 小 28 个字节的原因，即防止封包后的以太网帧超过 eth0 的 MTU, 而在经过 eth0 时被丢弃。 网络包经过主机网络从节点 master-01 到达节点 worker-01。 主机 worker-01 收到 UDP 报文后， Linux 内核通过 UDP 端口号 8285 将包交给正在监听的 flanneld 。 运行在 worker-01 中的 flanneld 将 UDP 包解封包后得到 IP 包：10.244.0.6 → 10.244.1.4。 解封包后的 IP 包匹配到主机 worker-01 上的路由规则 （ 10.244.1.0 ），内核通过查本机路由表知道应该将 IP 包发送到 cni0 网桥 。 cni0 网桥将 IP 包转发给连接在该网桥上的容器 B ，至此整个流程结束 。 回程报文将按上面的数据流原路返回 。 在整个通信过程中， flanneld 在其中主要起到的作用是：\nUDP 封包解包； 节点上的路由表的动态更新 。 flannel 的 UDP 封装是指原始数据由起始节点的 flanne ld 进行 UDP 封装，经过主机网络投递到目的节点后就被另一端的 flanneld 还原成了原始的数据包，两边的 容器 都感觉不到这个过程的存在 。 flannel 根据 etcd 的数据刷新本节点路由表，通过路由表信息在寻址时找到应该投递的目标节点 。\n容器 A 和容器 B 虽然在物理网络上并没有直接相连，但在逻辑上就好像是处于同一个三层网络中，这种基于底层物理网络设备通过 flannel 等软件定义网络技术构建的上层网络称之为 overlay 网络 。\n接口抓包分析 master-01 cni0 接口抓包分析\n1 tcpdump -nni cni0 -s0 -vv -w flannel-udp-master-cni.pcap master-01 eth0 接口抓包分析\n1 tcpdump -nni ens192 -s0 -vv -w flannel-udp-master-eth0.pcap 通过 UDP 这种 backend 实现的网络传输过程最明显的问题是网络数据包先通过 flannel0 设备从用户态复制到内核态，再由内核态复制到用户态的应用，仅一次网络传输就进行了两次用户态和内核态的切换，显然效率是不高的。\n那么，有没有比 UDP 模式更高效的办法呢？能否把 封包／解包 这些事情都交给 Linux 内核去做，而不是让 flanneld 代劳呢？事实上， Linux 内核本身也提供了比较成熟的网络封包／解包（隧道传输）实现方案 VXLAN。\nBackend: VXLAN 修改 flannel 配置文件 1 2 3 4 5 6 7 net-conf.json: | { \u0026#34;Network\u0026#34;: \u0026#34;10.244.0.0/16\u0026#34;, \u0026#34;Backend\u0026#34;: { \u0026#34;Type\u0026#34;: \u0026#34;vxlan\u0026#34; } } 部署 flannel 网络插件 1 kubectl apply -f kube-flannel-vxlan.yml 查看 flannel Pod 状态 验证当前运行模式\nVXLAN 模式数据路径 Node Name node-ip node-mac pod-id Pod Name cni0 master-01 172.16.66.31 00:50:56:a4:04:46 10.244.0.6 A 10.244.0.1 worker-01 172.16.66.32 00:50:56:a4:b8:22 10.244.1.4 B 10.244.1.1 worker-01 172.16.66.32 00:50:56:a4:b8:22 10.244.1.5 C 10.244.1.1 同 UDP Backend 模式，容器 A 中的 IP 包通过容器 A 内的路由表被发送到 cni0 。 到达 cni0 中的 IP 包通过匹配 worker-01 中的路由表发现通往 10.244.1.4 的 IP 包应该交给 flannel.1 接口 。 flannel.1 收到报文后将按照配置进行封包 。 首先，通过 etcd 得知 10.244.1.4 属于节点 worker-01 ，并得到节点 worker-01 的 IP 。 然后，通过节点 master-01 中的转发表得到节点 worker-01 对应的 MAC 地址，根据 flannel.1 设备创建时的设置参数进行 VXLAN 封包 。 通过 master-01 跟 worker-01 之间的网络连接， VXLAN 包到达 worker-01 的 eth0 接口 。 通过端口 8472, VXLAN 包被转发给 flannel.1 进行解包。 解封装后的 IP 包匹配 worker-01 中的路由 表（ 10.244.1.0 ），内核将 IP 包转发给 cni0 。 cni0 将 IP 包转发给连接在 cni0 上的容器 B 。 接口抓包分析 master-01 cni0 接口抓包分析\n1 tcpdump -nni cni0 -s0 -vv -w flannel-vxlan-master-cni.pcap master-01 eth0 接口抓包分析\n1 tcpdump -nni ens192 -s0 -vv -w flannel-vxlan-master-eth0.pcap Backend: host-gw Host Gateway 简称 host-gw ，从名字中就可以想到这种方式是通过把主机当作网关实现跨节点网络通信的 。 那么 ，具体如何实现跨节点通信呢？与 UDP 和 VXLAN 模式类似， 要使用 host-gw 模式，需要将 flannel 的Backend 中的 Type 参数设置成 “host-gw“。\n同 UDP 、 VXLAN 模式一致，通过容器 A 的路由 表 IP 包到达 cni0 。 到达 cni0 的 IP 包匹配到 master-01 中的路由规则（ 10.244.1.0 ），并且网关为 172.16.66.33 ，即 worker-01 ，所以内核将 IP 包发送给 worker-01 (172.16.66.33)。 IP 包通过物理网络到达主机 worker-01 的 eth0 。 到达主机 worker-01 eth0 的 IP 包匹配到 worker-01 中的路由 表（ 10.244.1.0 ) , IP 包被转发给cni0 。 cni0 将 IP 包转发给连接在 cni0 上的容器 B 。 host-gw 模式下，各个节点之间的跨节点网络通信要通过节点上的路由表实现，因此必须要通信双方所在的宿主机能够直接路由 。 这就要求 flannel host-gw 模式下集群中所有的节点必须处于同一个二层网络内， 这个限制使得 host-gw 模式无法适用于集群规模较大且需要对节点进行网段划分的场景 。 host-gw 的另外一个限制则是随着集群中节点规模的增大，flanneld 维护主机上成千上万条路由表的动态更新也是一个不小的压力 ，因此在路由方式下 ，路由表规则的数量是限制网络规模的一个重要因素。\nCalico Calico 在每一个计算节点利用 Linux 内核的一些能力实现了一个高效的 vRouter 负责数据转发，而每个 vRouter 通过 BGP 把 自己运行的工作负载的路由信息 向整个 Calico 网络传播 。 小规模部署可以直接互联，大规模下可以通过指定的 BGP Route Reflector 完成 。 最终保证所有的工作负载之间的数据流量都是通过 IP 路由的方式完成互联的 。 Calico 节点组网可以直接利用数据中心的网络结构（无论是 L2 还是 L3 ），不需要额外的 NAT 或隧道 。\n组件 Felix Felix 是一个守护程序，作为 agent 运行在托管容器或虚拟机的 Calico 节点上 。 Felix 负责刷新主机路由和 ACL 规则等，以便为该主机上的 Endpoint 正常运行提供所需的网络连接和管理。 进出容器 、 虚拟机和物理主机的所有流量都会遍历 Calico ，利用 Linux 内核原生的路由和 iptables 生成的规则 。\nBGP Client (BIRD )伯克利大学互联网路由守护进程 Calico 在每个运行 Felix 服务的节点上都部署一个 BGP Client ( BGP 客户端）。 BGP 客户端的作用是读取 Felix 编写到内核中的路由信息，由 BGP 客户端对这些路由信息进行分发 。 具体来说，当 Felix 将路由插入 Linux 内核 FIB 时， BGP 客户端将接收它们，并将它们分发到集群中的其他工作节点 。\nRoute Reflector 简单的 BGP 可能成为较大规模部署的性能瓶颈，因为它要求每个 BGP 客户端连接到网状拓扑中的每一个其他 BGP 客户端。 随着集群规模的增大，一些设备的路由表甚至会被撑满 。\n因此，在较大规模的部署中， Calico 建议使用 BGP Route Reflector （路由器反射器）。互联网中通常使用 BGP Route Reflector 充当 BGP 客户端连接的中心点，从而避免与互联网中的每个 BGP 客户端进行通信。\nCalico 的 IPIP 隧道模式 Calico 可以创建并管理一个 3 层平面网络，为每个工作负载分配一个完全可路由的 IP 地址 。 工作负载可以在没有 IP 封装或 NAT 的情况下进行通信，以实现裸机性能，简化故障排除和提供更好的互操作性 。 我们称这种网络管理模式为 vRouter 模式 。 vRouter 模式直接使用物理机作为虚拟路由器，不再创建额外的隧道 。 然而在需要使用 overlay 网络的环境中， Calico 也提供了 IP-in-IP （简称 ipip ）的隧道技术 。\n和其他 overlay 模式一样 ， ipip 是在各节点之间 “架起” 一个隧道，通过隧道两端节点上的容器网络连接，实现机制简单说就是用 IP 包头封装原始 IP 报文 。 启用 ipip 模式时 ，Calico 将在各个节点上创建一个名为 tunl0 的虚拟网络接口，如下图所示 。\n部署 calico 网络插件 1 kubectl apply -f calico-ipip.yaml 验证当前运行模式 如果有 tunl0 接口表示当前 calico 运行在 IPIP 模式\n抓包分析 Node Name node-ip node-mac pod-id Pod Name tunl0 master-01 172.16.66.35 00:0c:29:31:4e:dd 10.244.184.67 A 10.244.184.64 worker-01 172.16.66.36 00:0c:29:3c:3d:b9 10.244.171.2 B 10.244.171.0 worker-01 172.16.66.36 00:0c:29:3c:3d:b9 10.244.171.3 C 10.244.171.0 1 tcpdump -nni tunl0 -s0 -vv -w calico-ipip-master-tunl0.pcap 1 tcpdump -nni ens192 -s0 -vv -w calico-ipip-master-eth0.pcap Calico BGP 部署 calico 网络插件 1 kubectl apply -f calico-bgp.yaml 验证当前运行模式 抓包分析 1 tcpdump -nni ens192 -s0 -vv -w calico-bgp-master-eth0.pcap 附录 覆盖网络 [ overlay network ] 运行在一个网上的网（应用层网络），并不依靠ip地址来传递消息，而是采用一种映射机制，把ip地址和identifiers做映射来资源定位。\n底层的物理网络设备组成的网络我们称为 Underlay 网络，而用于虚拟机和云中的这些技术组成的网络称为 Overlay 网络，这是一种基于物理网络的虚拟化网络实现。\n修改 Wireshark 识别 VXLAN 协议 编辑 - 首选项 - 协议\n修改 wireshark 识别 8285 端口UDP包 Flannel 启动时报错 每台服务器都执行 解决方法一： 一次性 重启就失效\n1 2 # 手工加载内核模块 modprobe br_netfilter 解决方法二：开机自动加载 ，重启\n1 echo \u0026#34;br_netfilter\u0026#34; \u0026gt; /etc/modules-load.d/br_netfilter.conf Kubernetes 集群 IP 地址分配 Kubernetes 使用各种 IP 范围为节点、 Pod 和服务分配 IP 地址 。\n系统管理员为每个节点分配一个 IP 地址 。 该节点 IP 用于提供从数据面组件（如 Kube-proxy 和 Kubelet ）到 控制面 Kubernetes Master 的连接； 系统会为每个 Pod 分配一个地址块内的 IP 地址 。 用户可以在创建集群时通过参数 podSubnet 指定此范围； (podSubnet: 10.244.0.0/16) 系统会为每个服务（Service）分配一个 IP 地址（称为 ClusterIP ）。 用户可以在创建集群时通过参数 serviceSubnet 指定此范围； (serviceSubnet: 10.96.0.0/12) Pod 出站流量\nKubemetes 处理 Pod 的出站流量的方式主要分为以下三种：\nPod 到 Pod\n在 Kubemetes 集群中，每个 Pod 都有自己的 IP 地址，运行在 Pod 内的应用都可以使用标准的端口号，不用重新映射到不同的随机端口号 。 所有的 Pod 之间都可以保持三层网络的连通性，比如可以相互 ping 对方，相互发送 TCP/UDP/SCTP 数据包 。 CNI 就是用来实现这些网络功能的标准接口 。\nPod 到 Service\nPod 的生命周期很短暂，但客户需要的是可靠的服务，因此 Kubemetes 引 人了新的资源对象 Service ，其实它就是 Pod 前面的 4 层负载均衡器。 Service 总共有 4 种类型，其中最常用的类型是 ClusterIP ，这种类型的 Service 会自动分配一个仅集群内部可以访问的 虚拟IP 。\nKubemetes 通过 Kube-proxy 组件实现这些功能，每台计算节点上都运行一个 Kube-proxy 进程，通过复杂的 iptables/IPVS 规则在 Pod 和 Service 之间进行各种过滤和 NAT 。\nPod 到 集群外\n从 Pod 内部到集群外部的流量 ， Kubemetes 会通过 SNAT 来处理。 SNAT 做的工作就是将数据包的源从 Pod 内部的 IP:Port 替换为宿主机的 IP:Port 。 当数据包返回时，再将目的地址从宿主机的 IP:Port 替换为 Pod 内部的 IP:Port ，然后发送给 Pod ，当然，中间的整个过程对 Pod 来说是完全透明的，它们对地址转换不会有任何感知 。\nKubernetes 网络架构 谈到 Kubemetes 的网络模型，就不能不提它著名的 “单 Pod 单 IP”模型 ， 即每个 Pod 都有一个独立的IP， Pod 内所有容器共享 network namespace （同一个网络协议栈和 IP ）。\n“单 Pod 单 IP” 网络模型为我们勾勒了一个 Kubemetes 扁平网络的蓝图，在这个网络世界里：容器是一等公民 ， 容器之间直接通信，不需要额外的 NAT，因此不存在源地址被伪装的情况； Node 与容器网络直连， 同样不需要额外的 NAT 。 扁平化网络的优点在于：没有 NAT 带来的性能损耗，而且可追溯源地址，降低网络排错的难度 。\n总体而言，集群内访问 Pod ， 会经过 Service ； 集群外访问 Pod ， 经过的是 Ingress 。\nKubernetes 对网络的要求：\n所有容器都可以不用 NAT 的方式同别的容器通信。 所有节点都可以在不用 NAT 的方式下同所有容器通信，反之亦然。 容器的地址和别人看到的地址是同一个地址。 ","date":"2026-04-06T17:57:39+08:00","image":"https://cdn.jsdelivr.net/gh/luckycloveryh/picgo-bed@main/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260524195223_525_12.jpg","permalink":"https://luckycloveryh.github.io/fuyou/p/k8s-cni-container-network-interface/","title":"15 - CNI"},{"content":"控制面组件工作原理 Etcd 安装 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ETCD_VER=v3.5.21 # choose either URL GITHUB_URL=https://github.com/etcd-io/etcd/releases/download DOWNLOAD_URL=${GOOGLE_URL} rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz rm -rf /tmp/etcd-download-test \u0026amp;\u0026amp; mkdir -p /tmp/etcd-download-test curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz tar xzvf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /tmp/etcd-download-test --strip-components=1 /tmp/etcd-download-test/etcd --version /tmp/etcd-download-test/etcdctl version 启动服务 1 2 3 4 # ./etcd WARNING: Package \u0026#34;github.com/golang/protobuf/protoc-gen-go/generator\u0026#34; is deprecated. A future release of golang/protobuf will delete this package, which has long been excluded from the compatibility promise. 列出集群成员 1 2 3 4 5 6 # ./etcdctl --endpoints=localhost:2379 member list --write-out=table +------------------+---------+---------+-----------------------+-----------------------+------------+ | ID | STATUS | NAME | PEER ADDRS | CLIENT ADDRS | IS LEARNER | +------------------+---------+---------+-----------------------+-----------------------+------------+ | 8e9e05c52164694d | started | default | http://localhost:2380 | http://localhost:2379 | false | +------------------+---------+---------+-----------------------+-----------------------+------------+ 写入操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 # 写入数据 ./etcdctl --endpoints=localhost:2379 put /key1 val1 OK # 读取数据 ./etcdctl --endpoints=localhost:2379 get /key1 /key1 val1 # 按 Key 的前缀查询数据 ./etcdctl --endpoints=localhost:2379 get --prefix / /key1 val1 # 只显示键值 ./etcdctl --endpoints=localhost:2379 get --prefix / --keys-only /key1 # watch key 变化 ./etcdctl --endpoints=localhost:2379 watch --prefix / ./etcdctl --endpoints=localhost:2379 put /name k1s ./etcdctl --endpoints=localhost:2379 put /name k2s ./etcdctl --endpoints=localhost:2379 put /name k3s ./etcdctl --endpoints=localhost:2379 put /name k4s ./etcdctl --endpoints=localhost:2379 get /name -wjson ./etcdctl --endpoints=localhost:2379 watch --prefix /name --rev 1 PUT /name k1s PUT /name k2s PUT /name k3s PUT /name k4s 灾备 1 2 3 4 5 6 7 8 9 # 声明 环境变量 export ETCDCTL_API=3 export ENDPOINTS=localhost:2379 # 备份 $ etcdctl --endpoints=${ENDPOINTS} snapshot save /data/etcd_backup_dir/etcd-snapshot.db # 还原 $ etcdutl --data-dir=/data/etcd/etcd-restore snapshot restore /data/etcd_backup_dir/etcd-snapshot.db Kubernetes 数据如何保存在 etcd 中 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 export ETCDCTL_API=3 在pod里面访问 kubectl exec -it etcd-master-01 -n kube-system -- sh etcdctl --endpoints https://localhost:2379 --cert /etc/kubernetes/pki/etcd/server.crt --key /etc/kubernetes/pki/etcd/server.key --cacert /etc/kubernetes/pki/etcd/ca.crt get --keys-only --prefix / etcdctl --endpoints https://localhost:2379 --cert /etc/kubernetes/pki/etcd/server.crt --key /etc/kubernetes/pki/etcd/server.key --cacert /etc/kubernetes/pki/etcd/ca.crt get --keys-only --prefix /registry/pods/default/redis-pv-sts-0 etcdctl --endpoints https://localhost:2379 --cert /etc/kubernetes/pki/etcd/server.crt --key /etc/kubernetes/pki/etcd/server.key --cacert /etc/kubernetes/pki/etcd/ca.crt get /registry/pods/default/redis-pv-sts-0 etcdctl --endpoints https://localhost:2379 --cert /etc/kubernetes/pki/etcd/server.crt --key /etc/kubernetes/pki/etcd/server.key --cacert /etc/kubernetes/pki/etcd/ca.crt get /registry/configmaps/default/info Etcd 备份脚本\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #!/bin/bash ETCDCTL_PATH=\u0026#39;/usr/local/bin/etcdctl\u0026#39; ENDPOINTS=\u0026#39;https://192.168.200.153:2379\u0026#39; ETCD_DATA_DIR=\u0026#34;/var/lib/etcd\u0026#34; BACKUP_DIR=\u0026#34;/var/backups/kube_etcd/etcd-$(date +%Y-%m-%d-%H-%M-%S)\u0026#34; KEEPBACKUPNUMBER=\u0026#39;5\u0026#39; ETCDBACKUPPERIOD=\u0026#39;30\u0026#39; ETCDBACKUPSCIPT=\u0026#39;/usr/local/bin/kube-scripts\u0026#39; ETCDBACKUPHOUR=\u0026#39;\u0026#39; ETCDCTL_CERT=\u0026#34;/etc/ssl/etcd/ssl/admin-master1.pem\u0026#34; ETCDCTL_KEY=\u0026#34;/etc/ssl/etcd/ssl/admin-master1-key.pem\u0026#34; ETCDCTL_CA_FILE=\u0026#34;/etc/ssl/etcd/ssl/ca.pem\u0026#34; [ ! -d $BACKUP_DIR ] \u0026amp;\u0026amp; mkdir -p $BACKUP_DIR export ETCDCTL_API=2;$ETCDCTL_PATH backup --data-dir $ETCD_DATA_DIR --backup-dir $BACKUP_DIR sleep 3 { export ETCDCTL_API=3;$ETCDCTL_PATH --endpoints=\u0026#34;$ENDPOINTS\u0026#34; snapshot save $BACKUP_DIR/snapshot.db \\ --cacert=\u0026#34;$ETCDCTL_CA_FILE\u0026#34; \\ --cert=\u0026#34;$ETCDCTL_CERT\u0026#34; \\ --key=\u0026#34;$ETCDCTL_KEY\u0026#34; } \u0026gt; /dev/null sleep 3 cd $BACKUP_DIR/../;ls -lt |awk \u0026#39;{if(NR \u0026gt; \u0026#39;$KEEPBACKUPNUMBER\u0026#39;){print \u0026#34;rm -rf \u0026#34;$9}}\u0026#39;|sh if [[ ! $ETCDBACKUPHOUR ]]; then time=\u0026#34;*/$ETCDBACKUPPERIOD * * * *\u0026#34; else if [[ 0 == $ETCDBACKUPPERIOD ]];then time=\u0026#34;* */$ETCDBACKUPHOUR * * *\u0026#34; else time=\u0026#34;*/$ETCDBACKUPPERIOD */$ETCDBACKUPHOUR * * *\u0026#34; fi fi crontab -l | grep -v \u0026#39;#\u0026#39; \u0026gt; /tmp/file echo \u0026#34;$time sh $ETCDBACKUPSCIPT/etcd-backup.sh\u0026#34; \u0026gt;\u0026gt; /tmp/file \u0026amp;\u0026amp; awk \u0026#39; !x[$0]++{print \u0026gt; \u0026#34;/tmp/file\u0026#34;}\u0026#39; /tmp/file crontab /tmp/file rm -rf /tmp/file kube-apiserver kube-controller-manager kube-scheduler kubelet CRI CSI CNI 介绍 CRI 容器运行时接口\nCNI 容器网络接口\nCSI 容器存储接口\n阿里云CSI： https://www.alibabacloud.com/help/zh/ack/ack-managed-and-ack-dedicated/user-guide/csi-overview-1/\nRef 容器运行时 Containerd 安装与使用: https://mp.weixin.qq.com/s/d44ngzDbW_ublUsw-KrCJQ\nCRI 客户端 crictl 命令介绍: https://mp.weixin.qq.com/s/DYHSP8s-3PCUkVF-ierZtg\n容器命令行工具 nerdctl: https://mp.weixin.qq.com/s/OIC1M0FYd9IJJEKt2Yi6sQ\nKubernetes 集群备份 - Velero: https://mp.weixin.qq.com/s/7k2Eit-zIb3ZZfWMweSQaQ\nMinIO on Kuberntes: https://mp.weixin.qq.com/s/3UzDr3VmNxexED7hoST2sA\n","date":"2026-04-06T17:55:53+08:00","image":"https://cdn.jsdelivr.net/gh/luckycloveryh/picgo-bed@main/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260524195255_532_12.jpg","permalink":"https://luckycloveryh.github.io/fuyou/p/k8s-underlying-principle/","title":"15-底层原理"},{"content":"什么是有状态的应用 有的应用的状态信息不是很重要，即使不恢复状态也能够正常运行，这就是我们常说的“无状态应用”。“无状态应用”典型的例子就是 Nginx 这样的 Web 服务器，它只是处理 HTTP 请求，本身不生产数据（日志除外），不需要特意保存状态，无论以什么状态重启都能很好地对外提供服务。\n还有一些应用，运行状态信息就很重要了，如果因为重启而丢失了状态是绝对无法接受的，这样的应用就是“有状态应用”。“有状态应用”的例子也有很多，比如 Redis、MySQL 这样的数据库，它们的“状态”就是在内存或者磁盘上产生的数据，是应用的核心价值所在，如果不能够把这些数据及时保存再恢复，那绝对会是灾难性的后果。\n结合目前学到的知识思考一下：Deployment 加上 PersistentVolume，在 Kubernetes 里是不是可以轻松管理有状态的应用了呢？\n的确，用 Deployment 来保证高可用，用 PersistentVolume 来存储数据，确实可以部分达到管理“有状态应用”的目的。\n但是 Kubernetes 的眼光则更加全面和长远，它认为“状态”不仅仅是数据持久化，在集群化、分布式的场景里，还有多实例的依赖关系、启动顺序和网络标识等问题需要解决，而这些问题恰恰是 Deployment 力所不及的。\n只使用 Deployment，多个实例之间是无关的，启动的顺序不固定，Pod 的名字、IP 地址、域名也都是完全随机的，这正是“无状态应用”的特点。\n但对于“有状态应用”，多个实例之间可能存在依赖关系，比如 master/slave、active/passive，需要依次启动才能保证应用正常运行，外界的客户端也可能要使用固定的网络标识来访问实例，而且这些信息还必须要保证在 Pod 重启后不变。\n所以，Kubernetes 就在 Deployment 的基础之上定义了一个新的 API 对象，名字也很好理解，就叫 StatefulSet，专门用来管理有状态的应用。\n使用 YAML 描述 StatefulSet StatefulSet 也可以看做是 Deployment 的一个特例，它也不能直接用 kubectl create 创建样板文件，但它的对象描述和 Deployment 差不多，同样可以把 Deployment 适当修改一下，就变成了 StatefulSet 对象。\nYAML 文件头信息是：\n1 2 3 4 apiVersion: apps/v1 kind: StatefulSet metadata: name: xxx-sts 这是一个完整 Redis 的 StatefulSet：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 apiVersion: apps/v1 kind: StatefulSet metadata: name: redis-sts spec: serviceName: redis-svc # svc name replicas: 2 selector: # 标签选择器 matchLabels: app: redis-sts template: # pod 模板 metadata: labels: app: redis-sts spec: containers: - image: redis:5-alpine name: redis ports: - containerPort: 6379 我们会发现，YAML 文件里除了 kind 必须是“StatefulSet”，在 spec 里还多出了一个“serviceName”字段，其余的部分和 Deployment 是一模一样的，比如 replicas、selector、template 等等。\n如何在 Kubernetes 里使用 StatefulSet 让我们用 kubectl apply 创建 StatefulSet 对象，用 kubectl get 先看看它是什么样的：\n1 2 3 kubectl apply -f redis-sts.yml kubectl get sts kubectl get pod 从截图里能够看到，StatefulSet 所管理的 Pod 不再是随机的名字了，而是有了顺序编号，从 0 开始分别被命名为 redis-sts-0、redis-sts-1，Kubernetes 也会按照这个顺序依次创建（0 号比 1 号的 AGE 要长一点），这就解决了“有状态应用”的第一个问题：启动顺序。\n有了启动的先后顺序，应用该怎么知道自己的身份，进而确定互相之间的依赖关系呢？\nKubernetes 给出的方法是使用 hostname，也就是每个 Pod 里的主机名，让我们再用 kubectl exec 登录 Pod 内部看看：\n1 kubectl exec -it redis-sts-0 -- sh 有了这个唯一的名字，应用就可以自行决定依赖关系了，比如在这个 Redis 例子里，就可以让先启动的 0 号 Pod 是主实例，后启动的 1 号 Pod 是从实例。\n解决了启动顺序和依赖关系，还剩下第三个问题：网络标识，这就需要用到 Service 对象。\n在写 Service 对象的时候要小心一些，metadata.name 必须和 StatefulSet 里的 serviceName 相同，selector 里的标签也必须和 StatefulSet 里的一致：\n1 2 3 4 5 6 7 8 9 10 11 12 13 apiVersion: v1 kind: Service metadata: name: redis-svc spec: selector: app: redis-sts ports: - port: 6379 protocol: TCP targetPort: 6379 可以看到这个 Service 并没有什么特殊的地方，也是用标签选择器找到 StatefulSet 管理的两个 Pod，然后找到它们的 IP 地址。\n不过，StatefulSet 的奥秘就在它的域名上。\n在讲 Service 时我们学过，每个 Service 对象自己会有一个域名，格式是 “ 对象名. 命名空间.svc.cluster.local ”\n在 StatefulSet 的场景下，因为每个 Pod 也都拥有了固定的名称，所以每个 Pod 也可以分配一个固定的域名，格式是“Pod 名. 服务名. 命名空间.svc.cluster.local”。\n我们用 kubectl exec 进入 Pod 内部，用 ping 命令来验证一下：\n1 kubectl exec -it redis-sts-0 -- sh 显然，在 StatefulSet 里的这两个 Pod 都有了各自的域名，也就是稳定的网络标识。那么接下来，外部的客户端只要知道了 StatefulSet 对象，就可以用固定的编号去访问某个具体的实例了，虽然 Pod 的 IP 地址可能会变，但这个有编号的域名由 Service 对象维护，是稳定不变的。\n到这里，通过 StatefulSet 和 Service 的联合使用，Kubernetes 就解决了“有状态应用”的依赖关系、启动顺序和网络标识这三个问题，剩下的多实例之间内部沟通协调等事情就需要应用自己去想办法处理了。\n关于 Service，有一点需要注意一下。\nService 原本的目的是负载均衡，应该由它在 Pod 前面来转发流量，但是对 StatefulSet 来说，这项功能反而是不必要的，因为 Pod 已经有了稳定的域名，外界访问服务就不应该再通过 Service 这一层了。所以，从安全和节约系统资源的角度考虑，我们可以在 Service 里添加一个字段 clusterIP: None ，告诉 Kubernetes 不必再为这个对象分配 IP 地址。这种类型的 Service 对象也被称为 Headless Services （无头服务 ）。\n1 2 3 4 5 6 7 8 9 10 11 12 apiVersion: v1 kind: Service metadata: name: redis-svc-headless spec: clusterIP: None selector: app: redis-sts ports: - port: 6379 protocol: TCP targetPort: 6379 实现 StatefulSet 数据持久化 现在 StatefulSet 已经有了固定的名字、启动顺序和网络标识，只要再给它加上数据持久化功能，我们就可以实现对“有状态应用”的管理了。\n这里就需要用到存储时学的 PersistentVolume 和 NFS 的知识，我们可以很容易地定义 StorageClass，然后编写 PVC，再给 Pod 挂载 Volume。\n不过，为了强调持久化存储与 StatefulSet 的一对一绑定关系，Kubernetes 为 StatefulSet 专门定义了一个字段“volumeClaimTemplates”，直接把 PVC 定义嵌入 StatefulSet 的 YAML 文件里。这样能保证创建 StatefulSet 的同时，就会为每个 Pod 自动创建 PVC，让 StatefulSet 的可用性更高。\n“volumeClaimTemplates”这个字段可以把它和 Pod 的 template、Job 的 jobTemplate 对比起来学习，它其实也是一个“套娃”的对象组合结构，里面就是应用了 StorageClass 的普通 PVC 而已。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 apiVersion: apps/v1 kind: StatefulSet metadata: name: redis-pv-sts spec: serviceName: redis-pv-svc volumeClaimTemplates: # PVC 模板 - metadata: name: redis-100m-pvc spec: storageClassName: nfs-client-retained accessModes: - ReadWriteMany resources: requests: storage: 100Mi replicas: 2 selector: matchLabels: app: redis-pv-sts template: metadata: labels: app: redis-pv-sts spec: containers: - image: redis:5-alpine name: redis ports: - containerPort: 6379 volumeMounts: - name: redis-100m-pvc mountPath: /data 首先 StatefulSet 对象的名字是 redis-pv-sts，表示它使用了 PV 存储。然后“volumeClaimTemplates”里定义了一个 PVC，名字是 redis-100m-pvc，申请了 100MB 的 NFS 存储。在 Pod 模板里用 volumeMounts 引用了这个 PVC，把网盘挂载到了 /data 目录，也就是 Redis 的数据目录。\n最后使用 kubectl apply 创建这些对象，一个带持久化功能的“有状态应用”就算是运行起来了：\n1 kubectl apply -f redis-pv-sts.yml 你可以使用命令 kubectl get pvc 来查看 StatefulSet 关联的存储卷状态：\n这两个 PVC 的命名，不是随机的，是有规律的，用的是 PVC 名字加上 StatefulSet 的名字组合而成，所以即使 Pod 被销毁，因为它的名字不变，还能够找到这个 PVC，再次绑定使用之前存储的数据。\n用 kubectl exec 运行 Redis 的客户端，在里面添加一些 KV 数据：\n1 kubectl exec -it redis-pv-sts-0 -- redis-cli 现在我们模拟意外事故，删除这个 Pod：\n1 kubectl delete pod redis-pv-sts-0 由于 StatefulSet 和 Deployment 一样会监控 Pod 的实例，发现 Pod 数量少了就会很快创建出新的 Pod，并且名字、网络标识也都会和之前的 Pod 一模一样：\n再用 Redis 客户端登录去检查一下数据：\n因为我们把 NFS 网络存储挂载到了 Pod 的 /data 目录，Redis 就会定期把数据落盘保存，所以新创建的 Pod 再次挂载目录的时候会从备份文件里恢复数据，内存里的数据就恢复原状了。\n","date":"2026-04-06T17:52:09+08:00","image":"https://cdn.jsdelivr.net/gh/luckycloveryh/picgo-bed@main/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260524195236_531_12.jpg","permalink":"https://luckycloveryh.github.io/fuyou/p/k8s-statefulset-stateful-app/","title":"13 - StatefulSet"},{"content":"什么是 PersistentVolume 因为 Pod 里的容器是由镜像产生的，而镜像文件本身是只读的，进程要读写磁盘只能用一个临时的存储空间，一旦 Pod 销毁，临时存储也就会立即回收释放，数据也就丢失了。\n为了保证即使 Pod 销毁后重建数据依然存在，我们就需要找出一个解决方案，让 Pod产生的数据可以持久化存储。\n我们在 ConfiMap 的章节用过 Kubernetes 的 Volume ，它只是定义了有这么一个“存储卷”，而这个“存储卷”是什么类型、有多大容量、怎么存储，我们都可以自由发挥。Pod 不需要关心那些专业、复杂的细节，只要设置好 volumeMounts，就可以把 Volume 加载进容器里使用。\nKubernetes 就顺着 Volume 的概念，延伸出了 PersistentVolume 对象，它专门用来表示持久存储设备，但隐藏了存储的底层实现，我们只需要知道它能安全可靠地保管数据就可以了。\n作为存储的抽象，PV 实际上就是一些存储设备、文件系统，比如 Ceph、GlusterFS、MFS、NFS，或是本地磁盘，管理它们已经超出了 Kubernetes 的能力范围，所以，一般会由系统管理员单独维护，然后再在 Kubernetes 里创建对应的 PV。\nPV 属于集群的系统资源，是和 Node 平级的一种对象，Pod 对它没有管理权，只有使用权。\n什么是 PersistentVolumeClaim/StorageClass 有了 PV，我们是不是可以直接在 Pod 里挂载使用了呢？\n还不行。因为不同存储设备的差异实在是太大了：有的速度快，有的速度慢；有的可以共享读写，有的只能独占读写；有的容量小，只有几百 MB，有的容量大到 TB、PB 级别……\n这么多种存储设备，只用一个 PV 对象来管理还是有点太勉强了，不符合“单一职责”的原则，让 Pod 直接去选择 PV 也很不灵活。于是 Kubernetes 就又增加了两个新对象，PersistentVolumeClaim 和 StorageClass\nPersistentVolumeClaim，简称 PVC，从名字上看比较好理解，就是用来向 Kubernetes 申请存储资源的。PVC 是给 Pod 使用的对象，它相当于是 Pod 的代理，代表 Pod 向系统申请 PV。一旦资源申请成功，Kubernetes 就会把 PV 和 PVC 关联在一起，这个动作叫做“绑定”（bound）。\n系统里的存储资源非常多，如果要 PVC 去直接遍历查找合适的 PV 也很麻烦，所以就要用到 StorageClass。\nStorageClass 的作用有点像 IngressClass，它抽象了特定类型的存储系统（比如 Ceph、NFS），在 PVC 和 PV 之间充当“协调人”的角色，帮助 PVC 找到合适的 PV。也就是说它可以简化 Pod 挂载“虚拟盘”的过程，让 Pod 看不到 PV 的实现细节。\n使用 YAML 描述 PersistentVolume Kubernetes 里有很多种类型的 PV，我们先看看最容易的本机存储“HostPath”，它和 Docker 里挂载本地目录的 -v 参数非常类似，可以用它来初步认识一下 PV 的用法。\n因为 Pod 会在集群的任意节点上运行，所以首先，我们要在 Worker 节点上创建一个目录，它将会作为本地存储卷挂载到 Pod 里。\n在 /tmp 里创建 host-10m-pv 的目录，表示一个只有 10MB 容量的存储设备。\nPV 对象只能用 kubectl api-resources、kubectl explain 查看 PV 的字段说明，手动编写 PV 的 YAML 描述文件。\n1 2 3 4 5 6 7 8 9 10 11 12 apiVersion: v1 kind: PersistentVolume metadata: name: host-10m-pv spec: storageClassName: host-test accessModes: - ReadWriteOnce capacity: # 容量 storage: 10Mi hostPath: path: /tmp/host-10m-pv/ PV 对象的文件头部分很简单，还是 API 对象的老样子。\n“storageClassName”就是刚才说过的，对存储类型的抽象 StorageClass。这个 PV 是我们手动管理的，名字可以任意起，这里我写的是 host-test，你也可以把它改成 manual、hand-work 之类的词汇。\n“accessModes”定义了存储设备的访问模式，简单来说就是虚拟盘的读写权限，和 Linux 的文件访问模式差不多，目前 Kubernetes 里有 3 种：\nReadWriteOnce：存储卷可读可写，但只能被一个节点上的多个 Pod 挂载读写。 ReadOnlyMany：存储卷只读不可写，可以被任意节点上的多个 Pod 多次以只读方式挂载。 ReadWriteMany：存储卷可读可写，也可以被任意节点上的多次 Pod 挂载读写。 第三个字段“capacity”就很好理解了，表示存储设备的容量，这里我设置为 10MB。\n最后一个字段“hostPath”最简单，它指定了存储卷的本地路径，也就是我们在节点上创建的目录。\n使用 YAML 描述 PersistentVolumeClaim 有了 PV，就表示集群里有了这么一个持久化存储可以供 Pod 使用，我们需要再定义 PVC 对象，向 Kubernetes 申请存储。\n下面这份 YAML 就是一个 PVC，要求使用一个 10MB 的存储设备，访问模式是 ReadWriteOnce：\n1 2 3 4 5 6 7 8 9 10 11 apiVersion: v1 kind: PersistentVolumeClaim metadata: name: host-10m-pvc spec: storageClassName: host-test accessModes: - ReadWriteOnce resources: requests: storage: 10Mi PVC 的内容与 PV 很像，但它不表示实际的存储，而是一个“申请”或者“声明”，spec 里的字段描述的是对存储的“期望状态”。\n所以 PVC 里的 storageClassName、accessModes 和 PV 是一样的，但不会有字段 capacity，而是要用 resources.request 表示希望要有多大的容量。\n这样，Kubernetes 就会根据 PVC 里的描述，去找能够匹配 StorageClass 和容量的 PV，然后把 PV 和 PVC“绑定”在一起。\n在 Kubernetes 里使用 PersistentVolume 现在我们已经准备好了 PV 和 PVC，就可以让 Pod 实现持久化存储了。\n1 2 kubectl apply -f host-10m-pv.yaml kubectl get pv 接下来创建 PVC\n1 2 kubectl apply -f host-10m-pvc.yaml kubectl get pvc 一旦 PVC 对象创建成功，Kubernetes 就会立即通过 StorageClass、resources 等条件在集群里查找符合要求的 PV，如果找到合适的存储对象就会把它俩“绑定”在一起。\n如果没有找到满足条件的PV，PVC的状态会显示为 Pending 状态\n为 Pod 挂载 PersistentVolume PV 和 PVC 绑定好了，有了持久化存储，现在我们就可以为 Pod 挂载存储卷。\n使用方法和我们在ConfigMap章节学到的差不多，先要在 spec.volumes 定义存储卷，然后在 containers.volumeMounts 挂载进容器。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 apiVersion: apps/v1 kind: Deployment metadata: labels: app: nginx-dep name: nginx-dep spec: replicas: 1 selector: matchLabels: app: nginx-dep template: metadata: labels: app: nginx-dep spec: volumes: - name: host-pvc-vol persistentVolumeClaim: claimName: host-10m-pvc containers: - image: nginx:1.22.1 name: nginx volumeMounts: - name: host-pvc-vol mountPath: /tmp kubectl apply -f nginx-dep.yaml kubectl get pods -o wide 测试文件写入\n使用网络存储 要想让存储卷真正能被 Pod 任意挂载，我们需要变更存储的方式，不能限定在本地磁盘，而是要改成网络存储，这样 Pod 无论在哪里运行，只要知道 IP 地址或者域名，就可以通过网络通信访问存储设备。\n安装 NFS 服务器 1 2 3 4 5 6 7 dnf install -y nfs-utils # 所有的节点上 都要 安装 mkdir /data/nfs echo \u0026#34;/data/nfs 192.168.11.0/24(rw,sync,no_subtree_check,no_root_squash,insecure)\u0026#34; \u0026gt;\u0026gt; /etc/exports systemctl enable --now nfs-server 安装NFS 客户端 1 2 yum -y install nfs-utils showmount -e 172.16.66.53 客户端测试目录挂载\n如何使用 NFS 存储卷 现在我们已经为 Kubernetes 配置好了 NFS 存储系统，就可以使用它来创建新的 PV 存储对象了。\n先来手工分配一个存储卷，需要指定 storageClassName 是 nfs，而 accessModes 可以设置成 ReadWriteMany，这是由 NFS 的特性决定的，它支持多个节点同时访问一个共享目录。\n因为这个存储卷是 NFS 系统，所以我们还需要在 YAML 里添加 nfs 字段，指定 NFS 服务器的 IP 地址和共享目录名。\n这里我在 NFS 服务器的 /tmp/nfs 目录里又创建了一个新的目录 5g-pv，表示分配了 5GB 的可用存储空间，相应的，PV 里的 capacity 也要设置成同样的数值，也就是 5Gi。\n把这些字段都整理好后，我们就得到了一个使用 NFS 网络存储的 YAML 描述文件：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 apiVersion: v1 kind: PersistentVolume metadata: name: nfs-5g-pv spec: storageClassName: nfs accessModes: - ReadWriteMany capacity: storage: 5Gi nfs: path: /data/nfs/5g-pv server: 172.19.16.14 kubectl apply -f nfs-static-pv.yml kubectl get pv 有了 PV，我们就可以定义申请存储的 PVC 对象了，它的内容和 PV 差不多，但不涉及 NFS 存储的细节，只需要用 resources.request 来表示希望要有多大的容量，这里我写成 5GB，和 PV 的容量相同：\n1 2 3 4 5 6 7 8 9 10 11 12 13 apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nfs-static-pvc spec: storageClassName: nfs accessModes: - ReadWriteMany resources: requests: storage: 5Gi kubectl apply -f nfs-static-pvc.yaml kubectl get pvc 把 PVC 挂载到 Pod\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 apiVersion: apps/v1 kind: Deployment metadata: labels: app: nginx-dep name: nginx-dep spec: replicas: 1 selector: matchLabels: app: nginx-dep template: metadata: labels: app: nginx-dep spec: volumes: - name: nfs-pvc-vol persistentVolumeClaim: claimName: nfs-static-pvc containers: - image: nginx:1.22.1 name: nginx volumeMounts: - name: nfs-pvc-vol mountPath: /tmp 测试数据挂载\n如何部署 NFS Provisioner PV 还是需要人工管理，必须要由系统管理员手动维护各种存储设备，再根据开发需求逐个创建 PV，而且 PV 的大小也很难精确控制，容易出现空间不足或者空间浪费的情况。\n在我们的这个实验环境里，只有很少的 PV 需求，管理员可以很快分配 PV 存储卷，但是在一个大集群里，每天可能会有几百几千个应用需要 PV 存储，如果仍然用人力来管理分配存储，管理员很可能会忙得焦头烂额，导致分配存储的工作大量积压。\n2\n那么能不能让创建 PV 的工作也实现自动化呢？或者说，让计算机来代替人类来分配存储卷呢？\n这个在 Kubernetes 里就是“动态存储卷”的概念，它可以用 StorageClass 绑定一个 Provisioner 对象，而这个 Provisioner 就是一个能够自动管理存储、创建 PV 的应用，代替了原来系统管理员的手工劳动。\n有了“动态存储卷”的概念，前面我们讲的手工创建的 PV 就可以称为“静态存储卷”。\n目前，Kubernetes 里每类存储设备都有相应的 Provisioner 对象，对于 NFS 来说，它的 Provisioner 就是“NFS subdir external provisioner”，你可以在 GitHub 上找到这个项目。\n1 2 kubectl apply -f rbac.yaml kubectl apply -f deployment.yaml 如何使用 NFS 动态存储卷 我们来看一下 NFS 默认的 StorageClass 定义：\n1 2 3 4 5 6 7 8 apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-client provisioner: k8s-sigs.io/nfs-subdir-external-provisioner parameters: archiveOnDelete: \u0026#34;false\u0026#34; YAML 里的关键字段是 provisioner，它指定了应该使用哪个 Provisioner。另一个字段 parameters 是调节 Provisioner 运行的参数，需要参考文档来确定具体值，在这里的 archiveOnDelete: \u0026ldquo;false\u0026rdquo; 就是自动回收存储空间。\nParameters:\nName Description Default onDelete If it exists and has a delete value, delete the directory, if it exists and has a retain value, save the directory. will be archived with name on the share: archived-\u0026lt;volume.Name\u0026gt; archiveOnDelete If it exists and has a false value, delete the directory. if onDelete exists, archiveOnDelete will be ignored. will be archived with name on the share: archived-\u0026lt;volume.Name\u0026gt; pathPattern Specifies a template for creating a directory path via PVC metadata\u0026rsquo;s such as labels, annotations, name or namespace. To specify metadata use ${.PVC.}. Example: If folder should be named like -, use ${.PVC.namespace}-${.PVC.name} as pathPattern. n/a 字段 描述（Description） 默认值（Default） onDelete 如果该参数存在且值为 delete，则删除目录； 如果值为 retain，则保留目录。 会以 archived-\u0026lt;volume.Name\u0026gt; 的名称归档 archiveOnDelete 如果该参数存在且值为 false，则删除目录。 如果 onDelete 参数已存在，则 archiveOnDelete 会被忽略。 会以 archived-\u0026lt;volume.Name\u0026gt; 的名称归档 pathPattern 用于通过 PVC 的元数据（labels、annotations、name、namespace 等）自定义目录路径模板。 示例：如果想让目录命名为 \u0026lt;命名空间\u0026gt;-\u0026lt;pvc名称\u0026gt;，则使用 ${.PVC.namespace}-${.PVC.name} 作为 pathPattern。 n/a（不适用） 理解了 StorageClass 的 YAML 之后，你也可以不使用默认的 StorageClass，而是根据自己的需求，任意定制具有不同存储特性的 StorageClass，比如添加字段 onDelete: \u0026ldquo;retain\u0026rdquo; 暂时保留分配的存储，之后再手动删除：\n1 2 3 4 5 6 7 8 apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-client-retained provisioner: k8s-sigs.io/nfs-subdir-external-provisioner parameters: onDelete: \u0026#34;retain\u0026#34; 接下来我们定义一个 PVC，向系统申请 10MB 的存储空间，使用的 StorageClass 是默认的 nfs-client：\n1 2 3 4 5 6 7 8 9 10 11 apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nfs-dynamic-pvc spec: storageClassName: nfs-client-retained accessModes: - ReadWriteMany resources: requests: storage: 10Mi ","date":"2026-04-06T17:47:38+08:00","image":"https://cdn.jsdelivr.net/gh/luckycloveryh/picgo-bed@main/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260524195233_530_12.jpg","permalink":"https://luckycloveryh.github.io/fuyou/p/k8s-storage-persistent-volume/","title":"12 - 存储：解决数据持久化问题"},{"content":"Gateway API 介绍 Kubernetes Gateway API 是一种规范，用于在 Kubernetes 集群中管理流量路由。它是由 Kubernetes SIG-NETWORK 小组创建的，作为 Ingress 的替代方案。Gateway API 可以更轻松地处理入口流量、负载均衡、服务发现和流量路由等内容。\nGateway API 引入了一种面向角色的模型，该模型在基础设施提供商、集群维护者和应用程序开发人员之间划分职责。它定义了网关资源，例如 GatewayClass、Gateway 和 Routes，从而实现对管理流量和服务网络的细粒度控制。Gateway API 支持多种协议、改进的路由配置以及与服务网格架构的集成功能，为传统入口网关 API 提供了现代、可扩展的替代方案。\n基础设施提供者 ：云服务提供商或基础设施厂商，负责提供 GatewayClass 实现\n集群运维人员 ：管理集群资源，创建和维护 Gateway 实例\n应用程序开发者 ：开发和部署应用程序，定义应用的路由需求\n应用管理员 ：管理复杂应用系统，负责应用级别的策略配置\nGateway API 主要由三个组件组成：\nGatewayClass: 是集群级别的资源，定义一组具有共同配置和行为的网关。它类似于 IngressClass 或 StorageClass，由基础设施提供方创建。\nGateway: 描述如何将外部流量路由到集群内的服务。\nHTTPRoute： 是最常用的路由类型，用于处理 HTTP 和 HTTPS 流量\nGateway API 是如何工作的 Gateway Controller 与 Ingress Controller 类似， Gateway Controller 是管理所有网关资源的核心组件。它负责确保正确实现网关资源中定义的配置。 Gateway Controller 监听 Gateway、GatewayClass 和 Route 对象中的更改，然后相应地更新网络配置以处理定义的传入流量。这使 Kubernetes 集群能够对路由配置的变化做出反应并有效地管理流量。\nGateway Controller的实现方案：https://gateway-api.sigs.k8s.io/implementations/#gateway-controller-implementation-status\nGatewayClass GatewayClass 定义集群中 Gateway 的行为和配置，可以将其视为 Gateway 资源的模板。它指定哪个控制器来管理 Gateway （可以是 Istio 、Traefik、Kong 等开源方案或自定义实现）。GatewayClass 还包含 IP 池和资源限制等配置，基础设施提供商负责定义和管理 GatewayClass。\n1 2 3 4 5 6 apiVersion: gateway.networking.k8s.io/v1 kind: GatewayClass metadata: name: example-gatewayclass spec: controller: example.com/gateway-controller Gateway 定义 GatewayClass 后，就可以基于它创建一个 Gateway 对象。Gateway 对象是根据特定的 GatewayClass 创建的。它定义了流量如何进入集群以及哪些侦听器将处理流量（例如 HTTP、HTTPS）。单个网关可以处理多个应用程序的流量，这些应用程序可以共享同一个网关。\n1 2 3 4 5 6 7 8 9 10 apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: example-gateway spec: gatewayClassName: example-gatewayclass listeners: - name: http port: 80 protocol: HTTP 上面的声明定义了一个名为 example-gateway 的网关，它使用之前创建的 example-gatewayclass，使用 HTTP 协议监听 80 端口来处理传入流量。\nHTTPRoute 设置 Gateway 后，定义 HTTPRoutes（或其他路由类型，如 TCPRoute）以指定流量应如何转发到集群中不同的服务上。HTTPRoute 对象定义路由 HTTP 流量的规则，例如匹配路径、Header 或其他 HTTP 属性。HTTPRoute 对象负责把流量路由到 Kubernetes 中具体的 Service 对象上。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: example-httproute spec: hosts: - \u0026#34;example.com\u0026#34; rules: - matches: - path: type: Prefix value: /app forwardTo: - serviceName: app-service port: 80 这定义了一个名为 example-httproute 的 HTTPRoute，它监听对 example.com 的请求，将路径为 /app 的请求转发到 app-service 服务的 80 端口上。\n上述组件协同工作，可以更轻松地管理 Kubernetes 中的流量路由。使团队能够更好地控制网络流量的传输方式以及如何处理安全性。这样设计的目的是使 基础设施提供商、集群维护者和应用程序开发人员等不同团队能够顺利协作，而不会干扰彼此的工作。\n安装和使用 Gateway API 1 安装 Gateway API CRD 1 kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/standard-install.yaml 2 安装 Kong Kubernetes Ingress Controller Kong Ingress Controller 即可以做 Ingress 控制器，也可以做 Gateway 控制器，具体可以参照 Kong 官网文档：https://developer.konghq.com/kubernetes-ingress-controller。\n1 2 3 helm repo add kong https://charts.konghq.com helm repo update helm install kong kong/ingress -n kong --create-namespace 3 创建 GatewayClass kong-gatewayclass.yaml\n1 2 3 4 5 6 7 8 apiVersion: gateway.networking.k8s.io/v1 kind: GatewayClass metadata: name: kong annotations: konghq.com/gatewayclass-unmanaged: \u0026#39;true\u0026#39; spec: controllerName: konghq.com/kic-gateway-controller 4 创建 Gateway kong-gateway.yaml\n1 2 3 4 5 6 7 8 9 10 11 12 13 apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: kong spec: gatewayClassName: kong listeners: - name: proxy port: 80 protocol: HTTP allowedRoutes: namespaces: from: All 5 验证 Gateway Controller 是否安装成功 1 # kubectl get gatewayclass 6 案例一 HTTPRoute 创建前缀匹配路由 创建测试应用： demo-app.yaml\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 apiVersion: apps/v1 kind: Deployment metadata: name: go-httpbin namespace: default spec: replicas: 1 selector: matchLabels: app: go-httpbin template: metadata: labels: app: go-httpbin version: v1 spec: containers: - image: ccr.ccs.tencentyun.com/chijinjing/go-httpbin:v3 args: - \u0026#34;--port=8090\u0026#34; - \u0026#34;--version=v1\u0026#34; imagePullPolicy: Always name: go-httpbin ports: - containerPort: 8090 --- apiVersion: v1 kind: Service metadata: name: go-httpbin namespace: default spec: ports: - port: 80 targetPort: 8090 protocol: TCP selector: app: go-httpbin 创建 HTTPRoute demo-route.yaml，匹配前缀正确的请求路由到后端应用。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 apiVersion: gateway.networking.k8s.io/v1beta1 kind: HTTPRoute metadata: name: demo-route spec: parentRefs: # 引用Gateway资源。 - group: gateway.networking.k8s.io kind: Gateway name: kong hostnames: - demo.chijinjing.cn # host设置为 demo.chijinjing.cn rules: - matches: # 匹配规则为前缀匹配路径。 - path: type: PathPrefix value: / backendRefs: # 后端为类型为Service，名称go-httpbin，端口80。 - kind: Service name: go-httpbin port: 80 创建测试应用和 HTTPRoute 资源\n1 kubectl apply -f demo-app.yaml -f demo-route.yaml 验证前先配置域名解析， 生产环境建议使用 LoadBalancer，测试环境我们就直接使用 NodePort 的方式访问。\n1 kong-gateway-proxy LoadBalancer 10.96.3.39 \u0026lt;pending\u0026gt; 80:31302/TCP,443:32380/TCP 75m 修改 /etc/hosts 文件，添加 Node-ip demo.chijinjing.cn\n1 2 3 4 5 6 7 8 9 10 11 # curl http://demo.chijinjing.cn:31302/ headers: { \u0026#34;Accept\u0026#34;: [ \u0026#34;*/*\u0026#34; ], \u0026#34;Connection\u0026#34;: [ \u0026#34;keep-alive\u0026#34; ], \u0026#34;Host\u0026#34;: [ \u0026#34;demo.chijinjing.cn:31302\u0026#34; ], 7 案例二 HTTPRoute 创建权重路由 HTTPRoute 支持权重路由， 流量 按权重路由到不同的应用\n创建测试应用 app-weight.yaml\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 apiVersion: apps/v1 kind: Deployment metadata: name: old-nginx spec: replicas: 1 selector: matchLabels: run: old-nginx template: metadata: labels: run: old-nginx spec: containers: - image: registry.cn-hangzhou.aliyuncs.com/acs-sample/old-nginx imagePullPolicy: Always name: old-nginx ports: - containerPort: 80 protocol: TCP restartPolicy: Always --- apiVersion: v1 kind: Service metadata: name: old-nginx spec: ports: - port: 80 protocol: TCP targetPort: 80 selector: run: old-nginx sessionAffinity: None type: NodePort --- apiVersion: apps/v1 kind: Deployment metadata: name: new-nginx spec: replicas: 1 selector: matchLabels: run: new-nginx template: metadata: labels: run: new-nginx spec: containers: - image: registry.cn-hangzhou.aliyuncs.com/acs-sample/new-nginx imagePullPolicy: Always name: new-nginx ports: - containerPort: 80 protocol: TCP restartPolicy: Always --- apiVersion: v1 kind: Service metadata: name: new-nginx spec: ports: - port: 80 protocol: TCP targetPort: 80 selector: run: new-nginx sessionAffinity: None type: NodePort 创建 HTTPRoute route-weight.yaml\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 apiVersion: gateway.networking.k8s.io/v1beta1 kind: HTTPRoute metadata: name: demo-weight spec: parentRefs: # 引用 Gateway 资源。 - group: gateway.networking.k8s.io kind: Gateway name: kong hostnames: - weight.chijinjing.cn # host设置为 weight.example.com rules: - matches: # 匹配规则为前缀匹配路径。 - path: type: PathPrefix value: / backendRefs: # 设置后端以及对应权重，权重不为百分比形式，无需等于100。 - kind: Service name: old-nginx port: 80 weight: 1 # 设置old-nginx的权重为1。 - kind: Service name: new-nginx port: 80 weight: 1 # 设置new-nginx的权重为1。 部署\n1 kubectl apply -f app-weight.yaml -f route-weight.yaml 验证\n1 2 3 4 5 6 7 8 # curl weight.chijinjing.cn:31302 old # curl weight.chijinjing.cn:31302 new # curl weight.chijinjing.cn:31302 old # curl weight.chijinjing.cn:31302 new 8 案例三 HTTPRoute 修改请求头 使用 HTTPRoute 的 filter 能力，可以在请求或者回复阶段对请求头进行额外处理。本文以给发送到后端的请求添加请求头为例。\n创建HTTPRoute资源，demo-filter.yaml\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 apiVersion: gateway.networking.k8s.io/v1beta1 kind: HTTPRoute metadata: name: demo-filter spec: parentRefs: # 引用 Gateway 资源。 - group: gateway.networking.k8s.io kind: Gateway name: kong hostnames: - filter.chijinjing.cn # host设置为 filter.chijinjing.cn。 rules: - matches: # 匹配规则为前缀匹配路径。 - path: type: PathPrefix value: / filters: - type: RequestHeaderModifier # 添加请求头 my-header: foo。 requestHeaderModifier: add: - name: my-header value: foo backendRefs: # 后端为类型为 Service，名称go-httpbin，端口80。 - kind: Service name: go-httpbin port: 80 部署\n1 kubectl apply -f demo-filter.yaml 验证，在返回响应中可以看到我们添加的头部信息 My-Header。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 # curl filter.chijinjing.cn:31302 headers: { \u0026#34;Accept\u0026#34;: [ \u0026#34;*/*\u0026#34; ], \u0026#34;Connection\u0026#34;: [ \u0026#34;keep-alive\u0026#34; ], \u0026#34;Host\u0026#34;: [ \u0026#34;filter.chijinjing.cn:31302\u0026#34; ], \u0026#34;My-Header\u0026#34;: [ \u0026#34;foo\u0026#34; ], 9 案例四 配置 TLS 证书 修改 gateway 支持 HTTPS 协议\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 apiVersion: gateway.networking.k8s.io/v1beta1 kind: Gateway metadata: name: kong spec: gatewayClassName: kong listeners: - name: proxy port: 80 protocol: HTTP - name: proxy-ssl port: 443 protocol: HTTPS hostname: demo.chijinjing.cn tls: # 配置TLS。 mode: Terminate certificateRefs: # 引用所创建的secret。 - kind: Secret name: demo-tls-secret 创建 Secret\n1 2 3 kubectl create secret tls demo-tls-secret \\ --key demo.chijinjing.cn.key \\ --cert demo.chijinjing.cn_bundle.crt 使用案例一的域名验证 https://demo.chijinjing.cn:32380/ 可以看到如下返回信息，支持 HTTPS 协议了。\nGateway API 与 Ingress 对比 总结 通过前面的几个案例我们可以感觉到 Gateway API 确实比 Ingress 具有更强的路由能力和更细粒度的路由控制，通过 GatewayClass、Gateway和 HTTPRoute 三个核心组件，实现灵活的路由控制。在架构上 Gateway API 采用角色划分设计，让云厂商、运维和开发者各司其职，支持多协议和服务网格集成，比传统 Ingress 更易扩展和协作。\n","date":"2026-04-06T17:36:00+08:00","image":"https://cdn.jsdelivr.net/gh/luckycloveryh/picgo-bed@main/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260524195231_529_12.jpg","permalink":"https://luckycloveryh.github.io/fuyou/p/k8s-gateway-api-next-gen-ingress/","title":"下一代 Ingress —— Gateway API"},{"content":"Service 什么是 Service 在 Kubernetes 集群里 Pod 的生命周期是比较“短暂”的，虽然 Deployment 和 DaemonSet 可以维持 Pod 总体数量的稳定，但在运行过程中，难免会有 Pod 销毁又重建，这就会导致 Pod集合处于动态的变化之中。 Pod 的 IP 地址经常变来变去，客户端该怎么访问呢？如果不处理好这个问题，Deployment 和 DaemonSet 把 Pod 管理得再完善也是没有价值的。\n其实，这个问题在业内早就有解决方案来针对这样“不稳定”的后端服务，那就是“负载均衡”，典型的应用有 LVS、Nginx 等等。它们在前端与后端之间加入了一个“中间层”，屏蔽后端的变化，为前端提供一个稳定的服务。\n使用 YAML 描述 Service 用命令 kubectl api-resources 查看它的基本信息，可以知道它的简称是svc，apiVersion 是 v1。注意，这说明它与 Pod 一样，属于 Kubernetes 的核心对象。\n我们就可以写出 Service 的 YAML 文件头了\n1 2 3 4 apiVersion: v1 kind: Service metadata: name: xxx-svc 使用 kubectl expose 指令来创建service模板文件，需要用参数 \u0026ndash;port 和 \u0026ndash;target-port 分别指定映射端口和容器端口，而 Service 自己的 IP 地址和后端 Pod 的 IP 地址可以自动生成，用法上和Docker 的命令行参数 -p 很类似。\n1 2 3 4 5 6 7 8 9 10 11 12 kubectl expose deploy nginx-dep --port=80 --target-port=80 --dry-run=client -o yaml apiVersion: v1 kind: Service metadata: name: nginx-svc spec: selector: app: nginx-dep ports: - port: 80 targetPort: 80 protocol: TCP Service 的定义非常简单，在“spec”里只有两个关键字段，selector 和 ports。\nselector 是用来过滤出要代理的那些 Pod，因为我们指定要代理 Deployment，所以 Kubernetes 就为我们自动填上了 nginx-dep 的标签，会选择这个 Deployment 对象部署的所有 Pod。\n在 Kubernetes 里使用 Service 为了方便查看 Service 的效果，我们添加一个Nginx的配置文件，使用ConfigMap来定义，通过Volume 挂载到 Pod中。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 apiVersion: v1 kind: ConfigMap metadata: name: nginx-conf data: default.conf: | server { listen 80; location / { default_type text/plain; return 200 \u0026#39;srv : $server_addr:$server_port\\nhost: $hostname\\nuri : $request_method $host $request_uri\\ndate: $time_iso8601\\n\u0026#39;; } } 在 Deployment 中通过 “template.volumes”定义存储卷，使用“volumeMounts” 挂载到容器中。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 apiVersion: apps/v1 kind: Deployment metadata: labels: app: nginx-dep name: nginx-dep spec: replicas: 2 selector: matchLabels: app: nginx-dep template: metadata: labels: app: nginx-dep spec: volumes: - name: nginx-conf-vol configMap: name: nginx-conf containers: - image: nginx:1.22.1 name: nginx ports: - containerPort: 80 volumeMounts: - mountPath: /etc/nginx/conf.d name: nginx-conf-vol 部署 Deployment 之后，就可以 kubectl apply 创建 Service 对象了\n1 kubectl apply -f nginx-svc.yaml 使用命令 kubectl get svc 查看对象状态\nKubernetes 会自动为 Service 对象分配一个IP地址“10.111.193.124”，这个地址段是独立于Pod地址段的（在kubeadm 安装的配置文件里指定的）。Service 对象的 IP 地址还有一个特点，它是一个“虚地址”，不存在实体，只能用来转发流量。\n通过 kubectl describe 命令可以看到 Service 代理了哪些后端的 Pod：\n1 kubectl describe svc nginx-svc 通过截图可以看到 Service管理了两个 endpoint 对象，10.244.171.7:80 和 10.244.184.72:80，那这两个地址是不是实际 Pod 的地址呢？\n我们可以通过以下命令来验证\n1 kubectl get pods -o wide 通过上面的截图我们就能够验证 Service 确实用一个静态 IP 地址代理了两个 Pod 的动态 IP 地址。\n测试 Service 的负载均衡效果 我们使用 curl 命令在 master 节点或是 worker 节点上执行以下命令来访问 Service：\n1 curl 10.111.193.124 用 curl 访问 Service 的 IP 地址，就会看到它把数据转发给后端的 Pod，输出信息会显示具体是哪个 Pod 响应了请求，就表明 Service 确实完成了对 Pod 的负载均衡任务。\n我们再删除一个 Pod，看看 Service 是否会更新后端 Pod 的信息，实现自动化的服务发现：\n1 kubectl delete pod nginx-dep-b4bfd684c-pncv8 Pod 被删除后，Deployment 对象会自动创建一个新的 Pod，Service 会实时监控 Pod 的变化，所以它也会立即更新后端代理的 Pod 地址。这样后端的 Pod 数量就可以按业务需要自由伸缩，对用户是无感的。\n以域名的方式使用 Service Service 对象的 IP 地址是静态的，保持稳定，不过数字形式的 IP 地址用起来还是不太方便，如果要能有域名就更好了，DNS 在 Kubernetes 里能不能实现呢？\nKubernetes 有一个插件来实现 DNS 的功能，在早期这个插件普遍使用 kube-dns，现在用 coredns 比较多。\n因为 DNS 是一种层次结构，为了避免太多的域名导致冲突，Kubernetes 就把名字空间作为域名的一部分，减少了重名的可能性。\nService 对象的域名完全形式是 “对象名. 命名空间.svc.cluster.local”，但很多时候也可以省略后面的部分，直接写“对象名. 命名空间”甚至“对象名”也可以 。\nDNS 是在 Kubernetes 集群内部生效，所以要测试需要在 Pod 内验证。\n可以看到，现在我们就不再关心 Service 对象的 IP 地址，只需要知道它的名字，就可以用DNS 的方式去访问后端服务。\n如何让 Service 对外暴露服务 Service 是一种负载均衡技术，它不仅能够管理 Kubernetes 集群内部的服务，还能够向集群外部暴露服务。\nService 对象有一个关键字段“type”，表示 Service 是哪种类型的负载均衡。前面我们创建 Service 都使用的默认值“ClusterIP”，这种类型的 Service 地址只能在集群内访问。\n我们可以通过命令查看这个字段的属性\n1 kubectl explain service.spec.type 我们可以看到 默认值是 “ClusterIP”，另外还有三种类型分别是“ExternalName” 、“NodePort” 和 “LoadBalancer”。 其中 “LoadBalancer” 是由云服务商提供的，需要借助云服务商用才能实现完整的效果。\n我们分别测一下这几种类型的 Service。\nNodePort 我们修改一下 Service 的 YAML 文件，加上 “type” 字段：\n1 2 3 4 5 6 7 8 9 10 11 12 apiVersion: v1 kind: Service metadata: name: nginx-svc spec: selector: app: nginx-dep type: NodePort ports: - port: 80 targetPort: 80 protocol: TCP 更新对象，查看状态：\n1 2 kubectl apply -f nginx-svc.yaml kubectl get svc 可以看到 nginx-svc 的 “TYPE” 变成了 “NodePort”，而在 “PORT” 列里的端口信息也不一样，除了集群内部使用的“80”端口，还多出了一个“32119”端口，这就是 Kubernetes 在节点上为 Service 创建的专用映射端口。\n因为这个端口号属于节点，外部能够直接访问，所以现在我们就不需要登录集群节点或者进入 Pod 内部，直接在集群外使用任意一个节点的 IP 地址，就能够访问 Service 和它代理的后端服务了。\nExternalName ExternalName Service 是 Kubernetes 中一个特殊的 service 类型，它不需要指定 selector 去选择哪些 Pod 实例提供服务，而是使用 DNS CNAME 机制把自己 CNAME 到你指定的另外一个域名上，你可以提供集群内的名字，比如mysql.db.svc 这样的建立在db命名空间内的 MySQL 服务，也可以指定 http://www.baidu.com 这样的外部真实域名。\n我们需要使用 ping 和nslookup 命令，所以部署一个 busybox 的 deployment，busybox 镜像中提供很多常用的命令可以使用。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 apiVersion: apps/v1 kind: Deployment metadata: labels: app: busy-dep name: busy-dep spec: replicas: 1 selector: matchLabels: app: busy-dep template: metadata: labels: app: busy-dep spec: containers: - image: busybox name: busybox command: [\u0026#34;/bin/sh\u0026#34;,\u0026#34;-c\u0026#34;,\u0026#34;sleep 3600\u0026#34;] 创建一个 External Service\n1 2 3 4 5 6 7 apiVersion: v1 kind: Service metadata: name: external-svc spec: type: ExternalName externalName: www.baidu.com 登录到 Pod 中验证 External Service\n可以看到 external-svc 会被解析到一个 CNAME 指向 www.baidu.com\nLoadBalancer 我们修改 service 的类型为 LoadBalancer 看一下效果\n1 2 3 4 5 6 7 8 9 10 11 12 apiVersion: v1 kind: Service metadata: name: nginx-svc spec: selector: app: nginx-dep type: LoadBalancer ports: - port: 80 targetPort: 80 protocol: TCP 更新SVC\n1 kubectl apply -f nginx-svc-lb.yaml PORT 列没有变化，但是 EXTERNAL-IP 列会显示 pending，因为 LoadBalancer 类型的负载均衡需要云服务商提供，我们的环境会显示为 pending状态，但是 LoadBalancer 也是用了 NodePort 的实现方式，因为 PORT 列还保留了 NodePort 的端口。\n不同 Service 类型的对比 ClusterIP：只能在集群内部使用。clusterIP 域名 NodePort：可以作为集群流量入口，但也有一些缺点 端口数量有限。默认只在“30000~32767”这个范围内随机分配，只有 2000 多个，而且都不是标准端口号。 需要在每个节点上都开端口，然后使用 kube-proxy 路由到真正的后端 Service，这对于大规模集群来说如果Pod 频繁变更，Service 收敛速度就会变慢。 需要向外界暴露节点的 IP 地址，这在很多时候是不可行的，为了安全还需要在集群外再搭一个反向代理，增加了方案的复杂度。 虽然有这些缺点，但 NodePort 仍然是 Kubernetes 对外提供服务的一种简单易行的方式，在没有更好的方案出现之后，我们暂且使用这种方式。\nExternalName： 只是一个CNAME，应用场景有限。 LoadBalancer：依赖云厂商实现。 生产环境 Service 的三个 port 1 2 3 4 5 6 7 8 9 10 11 12 13 apiVersion: v1 kind: Service metadata: name: nginx-svc spec: selector: app: nginx-dep type: NodePort ports: - port: 80 # ClusterIP targetPort: 80 # 容器的端口 nodePort: 30080 # protocol: TCP Service 的几个 port 的概念很容易混淆，它们分别是 port 、 targetPort 和 NodePort 。\nport 表示 Service 暴露的服务端口，也是客户端访问用的端口，例如 Cluster IP:port 是提供给集群内部客户访问 Service 的入口 。 需要注意的是， port 不仅是 Cluster IP 上暴露的端口，还可以是 external IP 和 Load Balancer IP 。 Service 的 port 并不监听在节点 IP 上，即无法通过节点 IP:port 的方式访问 Service 。 NodePort 是 Kubemetes 提供给集群外部访问 Service 的入口的一种方式（另 一种方式是 Load Balancer ），所以可以通过 Node IP:nodePort 的方式提供集群外访问 Service 的入口 。 需要注意的是，我们这里说的集群外指的是 Pod 网段外，例如 Kubemetes 节点或因特网 。 targetPort 很好理解，它是应用程序实际监听 Pod 内流量的端口，从 port 和 NodePort 上到来的数据，最终经过 Kube-proxy 流入后端 Pod 的 targetPort 进入容器。 Service 实现原理 在 Kubernetes 集群中，每个 Node 运行一个 kube-proxy 进程。kube-proxy 负责为 Service 实现了一种 VIP（虚拟 IP）。\nService 底层技术 userspace 早期的版本中使用 iptables 默认 IPVS SVC 比较多的场景 ，建议使用 IPVS 模式 iptables 这种模式，kube-proxy 会监听 apiserver 对 Service 对象和 Endpoints 对象的添加和移除。对每个 Service，它会添加上 iptables 规则，从而捕获到达该 Service 的 clusterIP（虚拟 IP）和端口的请求，进而将请求重定向到 Service 的一组 backend 中的某一个 Pod 上面。我们还可以使用 Pod readiness 探针 验证后端 Pod 是否可以正常工作，以便 iptables 模式下的 kube-proxy 仅看到正常运行的后端，这样做意味着可以避免将流量通过 kube-proxy 发送到已知失败的 Pod 中，所以对于线上的应用来说一定要做 readiness 探针。\niptables 模式的 kube-proxy 默认的策略是，随机选择一个后端 Pod。\n比如当创建 backend Service 时，Kubernetes 会给它指派一个虚拟 IP 地址，比如 10.0.0.10。假设 Service 的端口是 1234，该 Service 会被集群中所有的 kube-proxy 实例观察到。当 kube-proxy 看到一个新的 Service，它会安装一系列的 iptables 规则，从 VIP 重定向到 Service 规则。 该 Service 规则连接到 Endpoint 规则，该 Endpoint 规则会重定向（目标 NAT）到后端的 Pod。\n查看 Kube-proxy 当前运行模式\n1 2 [root@master-01 manifests]# curl 127.0.0.1:10249/proxyMode iptables iptables 规则分析\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 [root@master-01 case1]# kubectl get pod -l app=tea -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES tea-7f5fcf84f8-55dgc 1/1 Running 0 132m 10.244.171.58 worker-01 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; tea-7f5fcf84f8-l7zxx 1/1 Running 0 132m 10.244.171.57 worker-01 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; tea-7f5fcf84f8-nhjq2 1/1 Running 0 132m 10.244.37.239 worker-02 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; [root@master-01 case1]# kubectl get svc tea-svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE tea-svc ClusterIP 10.107.48.138 \u0026lt;none\u0026gt; 80/TCP 108m -A KUBE-SERVICES -d 10.107.48.138/32 -p tcp -m comment --comment \u0026#34;default/tea-svc:http cluster IP\u0026#34; -m tcp --dport 80 -j KUBE-SVC-DVHFM6YVY2RW3DPQ -A KUBE-SVC-DVHFM6YVY2RW3DPQ -m comment --comment \u0026#34;default/tea-svc:http\u0026#34; -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-3NPNUAJQIUYNQ6GJ -A KUBE-SVC-DVHFM6YVY2RW3DPQ -m comment --comment \u0026#34;default/tea-svc:http\u0026#34; -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-62HFJKX5HLTTQHKU -A KUBE-SVC-DVHFM6YVY2RW3DPQ -m comment --comment \u0026#34;default/tea-svc:http\u0026#34; -j KUBE-SEP-I5YFUSH4L5RO5MII -A KUBE-SEP-3NPNUAJQIUYNQ6GJ -p tcp -m comment --comment \u0026#34;default/tea-svc:http\u0026#34; -m tcp -j DNAT --to-destination 10.244.171.57:8080 -A KUBE-SEP-62HFJKX5HLTTQHKU -p tcp -m comment --comment \u0026#34;default/tea-svc:http\u0026#34; -m tcp -j DNAT --to-destination 10.244.171.58:8080 -A KUBE-SEP-I5YFUSH4L5RO5MII -p tcp -m comment --comment \u0026#34;default/tea-svc:http\u0026#34; -m tcp -j DNAT --to-destination 10.244.37.239:8080 kube-proxy 针对 service 流量入口专门创建了 KUBE-SERVICES 链，\n1 -A KUBE-SERVICES -d 10.107.48.138/32 -p tcp -m comment --comment \u0026#34;default/tea-svc:http cluster IP\u0026#34; -m tcp --dport 80 -j KUBE-SVC-DVHFM6YVY2RW3DPQ 如果 访问地址 10.107.48.138/32，目标端口是 80，则会进入 KUBE-SVC-DVHFM6YVY2RW3DPQ 链\n1 2 3 4 5 -A KUBE-SVC-DVHFM6YVY2RW3DPQ -m comment --comment \u0026#34;default/tea-svc:http\u0026#34; -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-3NPNUAJQIUYNQ6GJ -A KUBE-SVC-DVHFM6YVY2RW3DPQ -m comment --comment \u0026#34;default/tea-svc:http\u0026#34; -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-62HFJKX5HLTTQHKU -A KUBE-SVC-DVHFM6YVY2RW3DPQ -m comment --comment \u0026#34;default/tea-svc:http\u0026#34; -j KUBE-SEP-I5YFUSH4L5RO5MII 这里利用了 iptables 的 random 模块，使连接有 33.3% 的概率进入 KUBE-SEP-3NPNUAJQIUYNQ6GJ 链，\n50% 概率进入 **KUBE-SEP-62HFJKX5HLTTQHKU 链，**最后会匹配 KUBE-SEP-I5YFUSH4L5RO5MII 链。\n因此，kube-proxy 的 iptables 模式采用随机数实现了服务的负载均衡。\nKUBE-SEP-3NPNUAJQIUYNQ6GJ 链的作用是 通过 DNAT 将请求发送到 10.244.171.57 的 8080 端口。\n同理，KUBE-SEP-62HFJKX5HLTTQHKU 链的作用是通过DNAT 将请求发送到 10.244.171.58 的 8080 端口。\nKUBE-SEP-I5YFUSH4L5RO5MII 链的作用是通过DNAT 将请求发送到 10.244.37.239 的 8080 端口。\n1 2 3 -A KUBE-SEP-3NPNUAJQIUYNQ6GJ -p tcp -m comment --comment \u0026#34;default/tea-svc:http\u0026#34; -m tcp -j DNAT --to-destination 10.244.171.57:8080 -A KUBE-SEP-62HFJKX5HLTTQHKU -p tcp -m comment --comment \u0026#34;default/tea-svc:http\u0026#34; -m tcp -j DNAT --to-destination 10.244.171.58:8080 -A KUBE-SEP-I5YFUSH4L5RO5MII -p tcp -m comment --comment \u0026#34;default/tea-svc:http\u0026#34; -m tcp -j DNAT --to-destination 10.244.37.239:8080 分析完 ClusterIP 的 iptables 规则后，接下来看一下 NodrPort 的访问方式。NodePort 的访问入口链是 KUBE-NODEPORTS，通过节点的 31668 端口访问 NodePort，则会进入 KUBE-SVC-I277KLBDTTJWT3KA 链，接下来的跳转跟 ClusterIP 方式类似。\n1 2 3 4 5 6 7 [root@master-01 ~]# kubectl get svc coffee-svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE coffee-svc NodePort 10.101.15.76 \u0026lt;none\u0026gt; 80:31668/TCP 5h17m -A KUBE-NODEPORTS -p tcp -m comment --comment \u0026#34;default/coffee-svc:http\u0026#34; -m tcp --dport 31668 -j KUBE-SVC-I277KLBDTTJWT3KA -A KUBE-SVC-I277KLBDTTJWT3KA -m comment --comment \u0026#34;default/coffee-svc:http\u0026#34; -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-XM2ZPQCWY7D6I5CQ 综上所述， iptables 模式最主要的链是 KUBE-SERVICES 、 KUBE-SVC-＊和 KUBE-SEP-＊ 。\nKUBE-SERVICES 链是访问集群内服务的数据包入口点，它会根据匹配到的目标 IP :port 将数据包分发到相应的 KUBE-SVC-＊链； KUBE-SVC-＊链相当于一个负载均衡器，它会将数据包平均分发到 KUBE-SEP-＊ 链 。 每个 KUBE-SVC-＊ 链后面的 KUBE-SEP-＊链都和 Service 的后端 Pod 数量一样； KUBE-SEP-＊链通过 DNAT 将连接的目的地址和端口从 Service 的 IP:port 替换为后端Pod 的 IP : port ，从而将流量转发到相应的 Pod 。 我们用下图总结 Kube-proxy iptables 模式的工作流 ，演示了从客户端 Pod 到不同节点上的服务器 Pod 的流量路径 。 客户端通过 172.16.12.100:80 连接到服务 。\nKubernetes API Server 会维护一个运行应用的后端 Pod 列表 。 每个节点上的 Kube-proxy 进程会根据 Service 和对应的 Endpoints 创建一系列的 iptables 规则 ，以将流量重定向到相应Pod （ 例如 10. 255. 255. 202: 8080 ） 。 整个过程客户端 Pod 无须感知集群的拓扑或集群内Pod 的任何 IP 信息 。\niptables 模式与 userspace 模式相比虽然在稳定性和性能上均有不小的提升，但因为 iptables 使用 NAT 完成转发， 也存在不可忽视的性能损耗 。 另外，当集群 中存在上万服务 时，Node 上的 iptables rules 会非常庞大，对管理是个不小的负担，性能还会大打折扣 。\nIPVS 除了 iptables 模式之外，kubernetes 也支持 ipvs 模式，在 ipvs 模式下，kube-proxy 监视 Kubernetes 服务和端点，调用 netlink 接口相应地创建 IPVS 规则， 并定期将 IPVS 规则与 Kubernetes 服务和端点同步。该控制循环可确保 IPVS 状态与所需状态匹配。访问服务时，IPVS 将流量定向到后端 Pod 之一。\nIPVS 代理模式基于类似于 iptables 模式的 netfilter 钩子函数，但是使用哈希表作为基础数据结构，并且在内核空间中工作。 所以与 iptables 模式下的 kube-proxy 相比，IPVS 模式下的 kube-proxy 重定向通信的延迟要短，并且在同步代理规则时具有更好的性能。与其他代理模式相比，IPVS 模式还支持更高的网络流量吞吐量。所以对于较大规模的集群会使用 ipvs 模式的 kube-proxy，只需要满足节点上运行 ipvs 的条件，然后我们就可以直接将 kube-proxy 的模式修改为 ipvs，如果不满足运行条件会自动降级为 iptables 模式，现在都推荐使用 ipvs 模式，可以大幅度提高 Service 性能。\nIPVS 提供了更多选项来平衡后端 Pod 的流量，默认是 rr，有如下一些策略：\nrr：轮询调度 lc：最小连接数 dh：目标哈希 sh：源哈希 sed：最短期望延迟 nq： 不排队调度 kube-proxy会监视 Kubernetes Service对象和Endpoints，调用netlink接口以相应地创建ipvs规则并定期与Kubernetes Service对象和Endpoints对象同步ipvs规则，以确保 ipvs 状态与期望一致。访问服务时，流量将被重定向到其中一个后端 Pod。\n与 iptables 类似，ipvs 基于netfilter 的 hook 功能，但使用哈希表作为底层数据结构并在内核空间中工作。这意味着ipvs可以更快地重定向流量，并且在同步代理规则时具有更好的性能。\n注意： ipvs模式假定在运行kube-proxy之前在节点上都已经安装了IPVS内核模块。当kube-proxy以ipvs代理模式启动时，kube-proxy将验证节点上是否安装了IPVS模块，如果未安装，则kube-proxy将回退到iptables代理模式。\n一个 IPVS 服务端口 80 到 Pod 端口 80 的映射的样例如下:\n1 2 3 4 5 [root@node-01 ~]# ipvsadm -ln IP Virtual Server version 1.2.1 (size=4096) TCP 10.111.109.105:80 rr -\u0026gt; 10.244.167.159:80 Masq 1 0 0 -\u0026gt; 10.244.167.165:80 Masq 1 0 0 iptabales vs IPVS ​ iptables 和 IPVS 在刷新服务路由规则上的时延对比\nKube-proxy 切换到 IPVS 模式 1 2 3 4 5 yum -y install ipvsadm ipset kubectl edit configmap kube-proxy -n kube-system # 默认是空代表用 iptables，可以修改成 ipvs mode: \u0026#34;ipvs\u0026#34; 重启 kube-proxy\n1 kubectl get pod -n kube-system|grep kube-proxy|awk \u0026#39;{system(\u0026#34;kubectl delete pod \u0026#34;$1\u0026#34; -n kube-system\u0026#34;)}\u0026#39; Endpoint Endpoint（端点） 是核心的网络资源对象，本质是「Service 与 Pod 之间的 “桥梁”」—— 它存储了对应 Service 要转发流量的 Pod IP + 端口 列表，让 Service 知道该把请求发给哪些具体的 Pod。\nIngress 什么是 Ingress Service 本质上是一个由 kube-proxy 控制的四层负载均衡，但在四层上的负载均衡功能还是太有限了，只能够依据 IP 地址和端口号做一些简单的判断和组合，而现在的绝大多数应用都是跑在七层的 HTTP/HTTPS 协议上的，有更多的高级路由条件，比如主机名、URI、请求头、证书等等。\nService 比较适合代理集群内部的服务。如果想要把服务暴露到集群外部，就只能使用 NodePort 或者 LoadBalancer 这两种方式 ，而这两种方式也都有各自的缺点，不能满足所有的场景。\nKubernetes 就引入一个新的 API 对象，在七层上做负载均衡。\nIngress 作为流量的总入口，统管集群的进出口数据\n什么是 Ingress Controller 我们前面讲过 Service 本身是没有服务能力的，它只是一些 iptables 规则，真正配置、应用这些规则的实际上是节点上的 kube-proxy 组件。\n同样的，Ingress 也只是声明了一些 HTTP 路由规则，相当于一份静态的配置文件，要把这些规则在集群里实施运行，还需要有另外一个组件，这就是 Ingress Controller，它的作用就相当于 Service 的 kube-proxy，能够读取、应用 Ingress 规则，处理、调度流量。\nIngress Controller 主要由社区来实现，比如我们熟悉的 Nginx， 就有 Nginx Ingress Controller。\n下图比较清楚地展示了 Ingress Controller 在 Kubernetes 集群中的地位。\n为什么要有 IngressClass 有了 Ingress 和 Ingress Controller，是不是就可以完美地管理集群的进出流量了呢？\n最初 Kubernetes 也是这么想的，一个集群里有一个 Ingress Controller，再给它配上许多不同的 Ingress 规则，应该就可以解决请求的路由和分发问题了。\n但随着 Ingress 在实践中的大量应用，很多用户发现这种用法会带来一些问题，比如：\n由于某些原因，项目组需要引入不同的 Ingress Controller，但 Kubernetes 不允许这样做； Ingress 规则太多，都交给一个 Ingress Controller 处理会让它不堪重负； 多个 Ingress 对象没有很好的逻辑分组方式，管理和维护成本很高； 集群里有不同的用户，他们对 Ingress 的需求差异很大甚至有冲突，无法部署在同一个 Ingress Controller 上。 Kubernetes 又提出了一个 Ingress Class 的概念，让它插在 Ingress 和 IngressController 中间，让它来协调流量规则和控制器，解除了 Ingress 和 Ingress Controller 的强绑定关系。\n现在，Kubernetes 用户可以转向管理 Ingress Class，用它来定义不同的业务逻辑分组，简化 Ingress 规则的复杂度。比如说，我们可以用 Class A 处理订单流量、Class B 处理物流流量、Class C 处理购物流量。\n部署 Ingress Controller Ingress Controller 是一个要实际干活、处理流量的应用程序，由 Deployment 对象来管理。\n由于 Kubernetes 官网维护的 Ingress NGINX Controller 已经退役不再维护，我们换成 Nginx 官方维护的\nNginx Ingress Controller，我们部署最新的稳定版本： 5.3.4\n部署 Nginx Ingress Controller\n1 2 3 4 5 6 7 kubectl apply -f deployments/common/ns-and-sa.yaml kubectl apply -f deployments/rbac/rbac.yaml kubectl apply -f deployments/common/nginx-config.yaml kubectl apply -f deployments/common/ingress-class.yaml kubectl apply -f deployments/deployment/nginx-ingress.yaml kubectl apply -f deployments/service/nodeport.yaml kubectl create -f deploy 检查部署结果\n1 2 3 $ kubectl get pods -n nginx-ingress NAME READY STATUS RESTARTS AGE nginx-ingress-55cc9cf8fc-jf6fh 1/1 Running 0 47m Nginx Ingress Controller 会将创建的对象存放在 nginx-ingress 命名空间中。\n使用 YAML 描述 Ingress 和 Ingress Class 首先用命令kubectl api-resources查看它们的基本信息：\n1 2 3 [root@master-01 ~]# kubectl api-resources | grep -i ingress ingressclasses networking.k8s.io/v1 false IngressClass ingresses ing networking.k8s.io/v1 true Ingress Ingress 和 Ingress Class 的 apiVersion 都是“networking.k8s.io/v1”，而且 Ingress 有一个简写“ing”。\n我们先使用命令创建一个 Ingress 对象\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 kubectl create ingress nginx-ing --rule=\u0026#34;nginx.xxhf.cc/=nginx-svc:80\u0026#34; --class=nginx --dry-run=client -o yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nginx-ing spec: ingressClassName: nginx rules: - host: nginx.xxhf.cc http: paths: - path: / pathType: Exact backend: service: name: nginx-svc port: number: 80 在这份 Ingress 的 YAML 里，有两个关键字段：“ingressClassName”和“rules”，分别对应了命令行参数，含义还是比较好理解的。\nIngress Class 本身并没有什么实际的功能，只是起到联系 Ingress 和 Ingress Controller 的作用，所以它的定义非常简单，在“spec”里只有一个必需的字段“controller”，表示要使用哪个 Ingress Controller。\n1 2 3 4 5 6 7 8 apiVersion: networking.k8s.io/v1 kind: IngressClass metadata: name: nginx # annotations: # ingressclass.kubernetes.io/is-default-class: \u0026#34;true\u0026#34; spec: controller: nginx.org/ingress-controller 创建 Ingress 规则 应用 YAML 清单文件：\n1 kubectl apply -f nginx-ingress.yaml 查看创建后的 ingress 对象\n1 2 3 $ kubectl get ingress NAME CLASS HOSTS ADDRESS PORTS AGE nginx-ing nginx nginx.xxhf.cc 80 51m 使用命令 kubectl describe 可以看到更详细的 Ingress 信息：\n1 kubectl describe ingress nginx-ing Ingress Class 在部署 nginx-ingress-controller 时已经自动创建了。\n1 2 3 $ kubectl get ingressclasses NAME CONTROLLER PARAMETERS AGE nginx nginx.org/ingress-controller \u0026lt;none\u0026gt; 54m 通过 ingress 来访问 我们部署的 Nginx 应用\n因为实际干活的是 controller，所以我们看一下 nginx-ingress-controller 对外提供服务的地址，NodePort SVC 对外暴露的 30080 和 30443 端口，ingress 作为七层入口主要是从集群外部访问。如果集群部署在云厂商的环境中，建议使用 LoadBalance 类型的 SVC。\n我们创建的 Ingress 规则是通过域名来访问 ，因此需要在客户端所在服务器上修改静态解析文件，添加如下行：\n1 192.168.11.57 nginx.xxhf.cc # 修改为自己 Node 节点 IP Case 1 : 使用 HTTP 协议访问 集群内应用 Case 2: 使用 HTTPS 协议访问 集群内应用 创建 TLS 类型 Secret\n1 2 3 kubectl create secret tls cafe-secret \\ --cert=cafe.xxhf.cc_bundle.crt \\ --key=cafe.xxhf.cc.key 给 ingress 添加 TLS 属性\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: cafe-ingress spec: ingressClassName: nginx tls: - hosts: - cafe.xxhf.cc # 域名 secretName: cafe-secret # TLS secret 名字 rules: - host: cafe.xxhf.cc http: paths: - path: /tea pathType: Prefix backend: service: name: tea-svc port: number: 80 - path: /coffee pathType: Prefix backend: service: name: coffee-svc port: number: 80 访问\nCase 3: 使用 annotaion 配置 Ingress 控制器 在 Kubernetes Ingress 里，annotation（注解） 就是给 Ingress 控制器 “传参数、发指令” 的配置方式，用来实现 路由、TLS、限流、重写、认证 等功能，不同 Ingress 控制器（nginx/treafik/apisix 等）支持不同 annotation。\n创建 htpasswd 文件 1 2 3 4 $ htpasswd -c htpasswd admin New password: # 输入密码 Re-type new password: Adding password for user admin 创建 secret 文件 1 2 3 4 5 6 7 8 kind: Secret apiVersion: v1 metadata: name: auth-passwd type: nginx.org/htpasswd stringData: htpasswd: | admin:$apr1$YUz1Uixv$y1xcvH.ls/VJhkI3KV7QD/ 创建 ingress 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-with-auth annotations: nginx.org/basic-auth-secret: \u0026#34;auth-passwd\u0026#34; nginx.org/basic-auth-realm: \u0026#39;Please Input Your Username and Password\u0026#39; spec: ingressClassName: nginx rules: - host: auth.xxhf.cc http: paths: - path: / pathType: Prefix backend: service: name: nginx-svc port: number: 80 测试连接 附录： 如何部署多个 ingress controller Kubernetes 社区维护 ingress-nginx\n1 2 3 4 [root@node-01 ~]# kubectl get ingressclasses.networking.k8s.io NAME CONTROLLER PARAMETERS AGE external-nginx k8s.io/ingress-nginx \u0026lt;none\u0026gt; 92s internal-nginx k8s.io/ingress-nginx \u0026lt;none\u0026gt; 102s 安装 NGINX 官方 Ingress Controller\n1 2 3 4 5 [root@node-01 kubernetes-ingress-3.4.2]# kubectl get ingressclasses.networking.k8s.io NAME CONTROLLER PARAMETERS AGE external-nginx k8s.io/external-ingress-nginx \u0026lt;none\u0026gt; 9h internal-nginx k8s.io/internal-ingress-nginx \u0026lt;none\u0026gt; 9h nginx nginx.org/ingress-controller \u0026lt;none\u0026gt; 3m3s Ingress Controller Kubernetes 社区维护： https://github.com/kubernetes/ingress-nginx\nNginx官方维护： https://docs.nginx.com/nginx-ingress-controller/\nTraefik: https://github.com/traefik/traefik\nLoadBalancer 生产环境 ingrss 入口流量 Kubernetes 中 ingress 请求入口流量详解: https://mp.weixin.qq.com/s/uyXugwITl6jMcol9Uw6mEg\n下一代 Ingress \u0026ndash; Gateway API https://mp.weixin.qq.com/s/-PPIdsSts2TlRzbecPxTHQ\n云原生代理 Traefik — Ingress Controller https://mp.weixin.qq.com/s/J82OHVdHs7XkwVs8zZzSfQ\nIngress NGINX 退役 Ingress NGINX 退役及替代方案： https://kubernetes.io/blog/2025/11/11/ingress-nginx-retirement/\n第三方 ingress 控制器：\nhttps://kubernetes.io/docs/concepts/services-networking/ingress-controllers/#third-party-ingress-controllers\nNGINX Ingress Controller 官方文档 https://docs.nginx.com/nginx-ingress-controller/install/manifests/\n","date":"2026-04-06T17:19:50+08:00","image":"https://cdn.jsdelivr.net/gh/luckycloveryh/picgo-bed@main/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260524195307_536_12.jpg","permalink":"https://luckycloveryh.github.io/fuyou/p/k8s-service-ingress-replica/","title":"10 - Service、Ingress - 找到你并不容易"},{"content":"DaemonSet 应用场景 DaemonSet 的目标是在集群的每个节点上运行且仅运行一个 Pod，就好像是为节点配上一只“看门狗”，忠实地“守护”着节点，这就是DaemonSet 名字的由来。\n应用场景：\n网络应用（如 kube-proxy），必须每个节点都运行一个 Pod，否则节点就无法加入Kubernetes 网络。 监控应用（如 node-exporter），必须每个节点都有一个 Pod 用来监控节点的状态，实时上报信息。 日志应用（如 filebeat），必须在每个节点上运行一个 Pod，才能够搜集容器运行时产生的日志数据。 安全应用，同样的，每个节点都要有一个 Pod 来执行安全审计、入侵检查、漏洞扫描等工作。 使用 YAML 描述 DaemonSet DaemonSet 和 Deployment 都属于在线业务，所以它们也都是“apps”组，使用命令kubectl api-resources可以知道它的简称是 ds ，所以 YAML 文件头信息应该是：\n1 2 3 4 apiVersion: apps/v1 kind: DaemonSet metadata: name: xxx-ds 我们从官网 找一个模板改一下\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 apiVersion: apps/v1 kind: DaemonSet metadata: name: node-exporter-ds labels: app: node-exporter spec: selector: matchLabels: app: node-exporter template: metadata: labels: app: node-exporter spec: containers: - name: node-exporter image: bitnami/node-exporter:1.6.1 # metrics pull resources: limits: cpu: 200m memory: 200Mi requests: cpu: 100m memory: 100Mi 我们比较一下Daemonset和Deployment的YAML就会看到，DaemonSet 在 spec 里没有 replicas 字段，这是它与Deployment 的一个关键不同点，意味着它不会在集群里创建多个 Pod 副本，而是要在每个节点上只创建出一个 Pod 实例。\n也就是说，DaemonSet 仅仅是在 Pod 的部署调度策略上和 Deployment 不同，其他的都是相同的，某种程度上我们也可以把 DaemonSet 看做是 Deployment 的一个特例。\n我们也可以用变通的方法来创建 DaemonSet 的 YAML 样板了，你只需要用 kubectl create 先创建出一个 Deployment 对象，然后把 kind 改成 DaemonSet，再删除 spec.replicas 就行了。\n部署 DaemonSet 我们执行命令 kubectl apply 来创建 DaemonSet 对象，再用 kubectl get 查看对象的状态：\n看这张截图，虽然我们没有指定 DaemonSet 里 Pod 要运行的数量，但它自己就会去查找集群里的节点，在节点里创建 Pod。因为我们的环境里有一个 Master 一个 Worker，所以 DaemonSet 就在每个节点上生成了一个 Pod。\n什么是 静态 Pod DaemonSet 是在 Kubernetes 里运行节点专属 Pod 最常用的方式，但它不是唯一的方式，Kubernetes 还支持另外一种叫“静态 Pod”的应用部署手段。 “静态 Pod”非常特殊，它不受 Kubernetes 系统的管控，不与 apiserver、scheduler 发生关系，所以是“静态”的。 但既然它是 Pod，也必然会“跑”在容器运行时上，也会有 YAML 文件来描述它，而唯一能够管理它的 Kubernetes 组件也就只有在每个节点上运行的 kubelet 了。\n“静态 Pod”的 YAML 文件默认都存放在节点的 /etc/kubernetes/manifests 目录下，它是Kubernetes 的专用目录。 下面的这张截图就是 Master 节点里目录的情况：\n你可以看到，Kubernetes 的 4 个核心组件 apiserver、etcd、scheduler、controller-manager原来都以静态 Pod 的形式存在的，这也是为什么它们能够先于 Kubernetes 集群启动的原因。 如果你有一些 DaemonSet 无法满足的特殊的需求，可以考虑使用静态 Pod，编写一个 YAML文件放到这个目录里，节点的 kubelet 会定期检查目录里的文件，发现变化就会调用容器运行时创建或者删除静态 Pod。\n","date":"2026-04-06T17:16:12+08:00","image":"https://cdn.jsdelivr.net/gh/luckycloveryh/picgo-bed@main/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260524195305_535_12.jpg","permalink":"https://luckycloveryh.github.io/fuyou/p/k8s-daemonset-node-daemon/","title":"09-DaemonSet-忠实可靠的看门狗"},{"content":"Deployment \u0026ldquo;Deployment”，是专门用来部署应用程序的，能够让应用永不宕机，多用来发布无状态的应用，是 Kubernetes 里最常用也是最有用的一个对象。\n为什么要有 Deployment 在线业务远不是单纯启动一个 Pod 这么简单，还有多实例、高可用、版本更新等许多复杂的操作。比如最简单的多实例需求，为了提高系统的服务能力，应对突发的流量和压力，我们需要创建多个应用的副本，还要即时监控它们的状态。如果还是只使用 Pod，那就会又走回手工管理的老路，没有利用好 Kubernetes 自动化运维的优势。\n使用 YAML 描述 Deployment 我们先用命令 kubectl api-resources 来看看 Deployment 的基本信息：\n1 2 [root@master-01 ~]# kubectl api-resources | grep deploy deployments deploy apps/v1 true Deployment Deployment 的简称是“deploy”，它的 apiVersion 是“apps/v1”，kind 是“Deployment”。\n按照前面的经验就可以知道YAML头部怎么写了\n1 2 3 4 apiVersion: apps/v1 kind: Deployment metadata: name: xxx-dep 也可以使用命令 kubectl create 来创建 Deployment 的 YAML 样板\n1 kubectl create deploy nginx-dep --image=nginx:1.22.1 --dry-run=client -o yaml 得到的YAML大概是这样：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 apiVersion: apps/v1 kind: Deployment metadata: labels: app: nginx-dep name: nginx-dep spec: replicas: 2 # 副本数， 2个 Pod selector: matchLabels: app: nginx-dep template: # Pod 的模板 metadata: labels: app: nginx-dep spec: containers: - image: nginx:1.22.1 name: nginx Deployment 的关键字段 replicas 字段的含义就是“副本数量”的意思，指定要在 Kubernetes 集群里运行多少个 Pod 实例。\nselector，它的作用是“筛选”出要被 Deployment 管理的 Pod 对象，下属字段“matchLabels”定义了 Pod 对象应该携带的 label，它必须和“template”里 Pod 定义的“labels”完全相同，否则 Deployment 就会找不到要控制的 Pod 对象，apiserver 也会告诉你 YAML 格式校验错误无法创建。\n1 2 3 4 5 6 7 8 9 10 11 spec: replicas: 2 selector: matchLabels: app: nginx-dep template: metadata: labels: app: nginx-dep spec: Kubernetes 采用的是“贴标签”的方式，通过在对象的“metadata”元信息里加各种标签（labels），我们就可以筛选出具有特定标识的那些对象。\n创建 Deployment 使用 kubectl apply 来创建对象\n1 kubectl apply -f nginx-dep.yaml 使用 kubectl get 命令 查看 Deployment\n1 kubectl get deployments 显示的信息很重要：\nREADY 表示运行的 Pod 数量，前面的数字是当前数量，后面的数字是期望数量，所以“2/2”的意思就是要求有两个 Pod 运行，现在已经启动了两个 Pod。 UP-TO-DATE 指的是当前已经更新到最新状态的 Pod 数量。因为如果要部署的 Pod 数量很多或者 Pod 启动比较慢，Deployment 完全生效需要一个过程，UP-TO-DATE 就表示现在有多少个 Pod 已经完成了部署，达成了模板里的“期望状态”。 AVAILABLE 要比 READY、UP-TO-DATE 更进一步，不仅要求已经运行，还必须是健康状态，能够正常对外提供服务，它才是我们最关心的 Deployment 指标。 AGE 表示 Deployment 从创建到现在所经过的时间，也就是运行的时间。 Deployment 管理的是 Pod，我们最终用的也是 Pod，所以还需要用 kubectl get pod 命令来看看 Pod 的状态：\n1 kubectl get pods 是时候来验证一下 Deployment 部署的应用是否真的可以做到“永不宕机”？\n1 kubectl delete pods nginx-dep-6f7d76ddc8-86g52 被删除的 Pod 确实是消失了，但 Kubernetes 在 Deployment 的管理之下，很快又创建出了一个新的 Pod，保证了应用实例的数量始终是我们在 YAML 里定义的数量。\n应用伸缩 在 Deployment 部署成功之后，你还可以随时调整 Pod 的数量，实现“应用伸缩”。\n1 kubectl scale deployment nginx-dep --replicas=5 应用滚动升级 如何定义应用版本 在 Kubernetes 里应用都是以 Pod 的形式运行的，而 Pod 通常又会被 Deployment 等对象来管理，所以应用的“版本更新”实际上更新的是整个 Pod。\n应用的版本变化就是 template 里 Pod 的变化，哪怕 template 里只变动了一个字段，那也会形成一个新的版本，也算是版本变化。\nKubernetes 使用了“摘要”功能，用摘要算法计算 template 的 Hash 值作为“版本号”。\n如何实现应用更新 部署应用 http-app:v1，实例数设为 4。\nhttp-app-v1.yaml\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 apiVersion: apps/v1 kind: Deployment metadata: labels: app: http-app-dep name: http-app-dep spec: replicas: 4 selector: matchLabels: app: http-app-dep template: metadata: labels: app: http-app-dep spec: containers: - image: registry.cn-beijing.aliyuncs.com/xxhf/http-app:v1 name: http-app 部署应用\n1 kubectl apply -f http-app-v1.yaml 可以通过 curl 观察到应用的版本\n1 2 3 4 5 # 端口映射 kubectl port-forward --address 0.0.0.0 deployment/http-app-dep 8080:80 # curl 127.0.0.1:8080 {\u0026#34;hostname\u0026#34;:\u0026#34;http-app-dep-68b9b69985-5j8pc\u0026#34;,\u0026#34;version\u0026#34;:\u0026#34;v1\u0026#34;} 编写一个新版本 http-app-v2.yaml ， 修改镜像的版本为 v2\n因为 Kubernetes 的动作太快了，为了能够观察到应用更新的过程，我们需要添加一个字段 minReadySeconds，让 Kubernetes 在更新过程中等待一点时间，确认 Pod 没问题才继续其余 Pod 的创建工作。\n1 2 3 4 5 6 7 8 9 10 11 apiVersion: apps/v1 kind: Deployment metadata: labels: app: http-app-dep name: http-app-dep spec: minReadySeconds: 30 # 确认Pod就绪的等待时间 ... - image: registry.cn-beijing.aliyuncs.com/xxhf/http-app:v2 name: http-app 执行命令 kubectl apply 来更新应用，因为改动了镜像名，Pod 模板变了，就会触发“版本更新”，然后用一个新命令：kubectl rollout status，来查看应用更新的状态\n1 2 kubectl apply -f http-app-v2.yaml kubectl rollout status deployment http-app-dep 再执行 kubectl get pod ，可以看到 pod 都更新成了新版本。\n仔细查看 kubectl rollout status 的输出信息，你可以发现，Kubernetes 不是把旧 Pod 全部销毁再一次性创建出新 Pod，而是在逐个地创建新 Pod，同时也在销毁旧 Pod，保证系统里始终有足够数量的 Pod 在运行，不会中断服务。\n新 Pod 数量增加的过程有点像是“滚雪球”，从零开始，越滚越大，所以这就是所谓的“滚动更新”（rolling update）。\n使用命令 kubectl describe 可以更清楚地看到 Pod 的变化情况：\n1 kubectl describe deployments http-app-dep 一开始的时候 V1 Pod（即 http-app-dep-68b9b69985）的数量是 4； 当“滚动更新”开始的时候，Kubernetes 创建 1 个 V2 Pod（即 http-app-dep-6c86b44b68 ），并且把 V1 Pod 数量减少到 3； 接着再增加 V2 Pod 的数量到 2，同时 V1 Pod 的数量变成了 1； 最后 V2 Pod 的数量达到预期值 4，V1 Pod 的数量变成了 0，整个更新过程就结束了 其实“滚动更新”就是由 Deployment 控制的两个同步进行的“应用伸缩”操作，老版本缩容到 0，同时新版本扩容到指定值，大家通过下面这张图再理解一下 滚动更新 的过程\n如何管理应用更新 在应用更新的过程中，我们可以随时使用kubectl rollout pause来暂停更新，检查、修改Pod，或者测试验证，如果确认没问题，再用 kubectl rollout resume 来继续更新。\n如果更新的版本比较多，我们想查看更新历史，可以使用命令 kubectl rollout history：\nkubectl rollout history 的列表输出的有用信息太少，可以在命令后加上参数 --revision 来查看每个版本的详细信息，包括标签、镜像名、环境变量、存储卷等等，通过这些就可以大致了解每次都变动了哪些关键字段：\n如果我们刚上线的v2版本发现有BUG，希望回退到V1版本，可以使用命令 kubectl rollout undo，也可以加上参数 --to-revision 回退到任意一个历史版本。\n1 kubectl rollout undo deployment http-app-dep kubectl rollout undo 的操作过程其实和 kubectl apply 是一样的，执行的仍然是“滚动更新”，只不过使用的是旧版本 Pod 模板，把新版本 Pod 数量收缩到 0，同时把老版本 Pod 扩展到指定值。\n下图是从 v2 到 v1 版本降级的过程：\n添加更新描述 kubectl rollout history 的版本列表好像有点太简单了呢？只有一个版本更新序号，而另一列 CHANGE-CAUSE 总是显示成 ，能不能加上说明信息，当然是可以的，只需要在 Deployment 的 metadata 里加上一个新的字段 annotations。\nannotations 字段的含义是“注解” “注释”，形式上和 labels 一样，都是 Key-Value，也都是给 API 对象附加一些额外的信息，但是用途上区别很大。\nannotations 添加的信息一般是给内部的各种对象使用的，有点像是“扩展属性”； labels 主要用来筛选、过滤对象的; annotations 里的值可以任意写，Kubernetes 会自动忽略不理解的 Key-Value，但要编写更新说明就需要使用特定的字段 kubernetes.io/change-cause。\n1 2 3 4 5 6 7 8 9 apiVersion: apps/v1 kind: Deployment metadata: name: http-app-dep labels: app: http-app-dep annotations: kubernetes.io/change-cause: version=v3 kubectl apply -f http-app-v3.yaml 控制滚动更新的参数 maxSurge表示在进行滚动更新期间，允许超过副本数量的额外Pod副本数。它指定了可以同时创建的超过副本数的Pod的最大数量。默认情况下，maxSurge的值为25%。例如，如果你的副本数为4，maxSurge设置为1，则在滚动更新期间，可以同时存在5个Pod。 maxUnavailable表示在进行滚动更新期间，允许不可用的最大Pod副本数。它指定了可以同时终止的不可用Pod的最大数量。默认情况下，maxUnavailable的值为25%。例如，如果你的副本数为4，并且maxUnavailable设置为1，那意味着在滚动更新期间，允许不可用的最大Pod副本数为1。这意味着在滚动更新期间，最多只能终止1个Pod，而剩下的3个Pod将保持可用状态。 http-app-v4.yaml\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 apiVersion: apps/v1 kind: Deployment metadata: name: http-app-dep labels: app: http-app-dep annotations: kubernetes.io/change-cause: version=v4 spec: minReadySeconds: 30 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 revisionHistoryLimit: 10 replicas: 4 应用部署策略 滚动部署 (k8s 原生支持 ) 在滚动部署中，应用的新版本逐步替换旧版本。实际的部署发生在一段时间内。在此期间，新旧版本会共存，而不会影响功能和用户体验。这个过程可以轻易的回滚。\n但是滚动升级有一个问题，在开始滚动升级后，流量会直接流向已经启动起来的新版本，但是这个时候，新版本是不一定可用的，比如需要进一步的测试才能确认。那么在滚动升级期间，整个系统就处于非常不稳定的状态，如果发现了问题，也比较难以确定是新版本还是老版本造成的问题。\n蓝绿部署 蓝绿发布提供了一种零宕机的部署方式。不停老版本，部署新版本进行测试，确认OK，将流量切到新版本，然后老版本同时也升级到新版本。始终有两个版本同时在线，有问题可以快速切换。\n蓝绿发布的特点：\n在部署应用的过程中，应用始终在线。并且新版本上线过程中，不会修改老版本的任何内容，在部署期间老版本状态不受影响。只要老版本的资源不被删除，可以在任何时间回滚到老版本。\n以下示意图可描述蓝绿发布的大致流程：先切分20%的流量到新版本，若表现正常，逐步增加流量占比，继续测试新版本表现。若新版本一直很稳定，那么将所有流量都切分到新版本，并下线老版本。\n切分20%的流量到新版本后，新版本出现异常，则快速将流量切回老版本。\n蓝绿部署要求在升级过程中，同时运行两套程序，对硬件的要求就是日常所需的二倍，比如日常运行时，需要10台服务器支撑业务，那么使用蓝绿部署，你就需要购置二十台服务器。\n金丝雀 / 灰度 发布 在生产环境上引一部分实际流量对一个新版本进行测试，测试新版本的性能和表现，在保证系统整体稳定运行的前提下，尽早发现新版本在实际环境上的问题。\n“为什么叫金丝雀发布呢，是因为金丝雀对矿场中的毒气比较敏感，所以在矿场开工前工人们会放一只金丝雀进去，以验证矿场是否存在毒气，这便是金丝雀发布名称的由来。”\n金丝雀发布的特点：\n金丝雀发布，又称为灰度发布。它能够缓慢的将修改推广到一小部分用户，验证没有问题后，再推广到全部用户，以降低生产环境引入新功能带来的风险。\n步骤一：部署少量副本的金丝雀版本的应用；\n步骤二：根据不同策略，将流量引入金丝雀副本。策略可以根据情况指定，比如随机样本策略（随机引入）、狗粮策略（就是内部用户或员工先尝鲜）、分区策略（不同区域用户使用不同版本）、用户特征策略（这种比较复杂，需要根据用户个人资料和特征进行分流，类似于千人千面）；\n步骤三：金丝雀副本应用 验证通过后，增加金丝雀应用的副本数，增加导流的比例，减少旧版本的流量和副本的数量，最终完成版本的切换。\n应用保障策略 容器资源配额 如何限制容器对资源的使用 Kubernetes如何限制容器对资源的需求，只要在 Pod 容器的描述部分添加一个新字段 resources 就可以了。\n1 2 3 4 5 6 7 8 9 10 containers: - image: nginx:1.22.1 name: nginx resources: limits: # 容器可以使用资源的上限 cgroups cpu: 200m memory: 100Mi requests: # 容器运行时需要满足的资源 cpu: 50m memory: 50Mi 需要重点学习的是 containers.resources，它下面有两个字段：\n“requests”，意思是容器要申请的资源，也就是说要求 Kubernetes 在创建 Pod 的时候必须分配这里列出的资源，否则容器就无法运行。 “limits”，意思是容器使用资源的上限，不能超过设定值，否则就有可能被强制停止运行。 内存的写法和磁盘容量一样，使用 Ki、Mi、Gi 来表示 KB、MB、GB，比如 512Ki、100Mi、0.5Gi 等。\nCPU 因为在计算机中数量有限，非常宝贵，所以 Kubernetes 允许容器精细分割 CPU，即可以 1 个、2 个地完整使用 CPU，也可以用小数 0.1、0.2 的方式来部分使用 CPU。不过 CPU 时间也不能无限分割，Kubernetes 里 CPU 的最小使用单位是 0.001，为了方便表示用了一个特别的单位 m，也就是“milli”“毫”的意思，比如说 500m 就相当于 0.5。 1000m=1核心\n如果 Pod 不写 resources 字段，Kubernetes 会如何处理呢？\n如果预估错误，Pod 申请的资源太多，系统无法满足会怎么样呢？\nWorker 节点资源不足，k8s 驱逐\n容器的服务质量 QoS 服务质量分为三类 Guaranteed Burstable BestEffort Guaranteed 的 Pod： Pod 中的每个容器都必须指定内存限制和内存请求。 对于 Pod 中的每个容器，内存****限制必须等于内存请求。 Pod 中的每个容器都必须指定 CPU 限制和 CPU 请求。 对于 Pod 中的每个容器，CPU 限制必须等于 CPU 请求。 1 2 3 4 5 6 7 8 9 10 11 12 containers: - image: nginx:1.22.1 name: nginx resources: limits: cpu: 200m memory: 100Mi requests: cpu: 200m memory: 100Mi [root@master-01 resource]# kubectl describe pods ngx-dep-res-q1-78fcfbfcbf-htscv QoS Class: Guaranteed Burstable 的 Pod Pod 不符合 Guaranteed QoS 类的标准。 Pod 中至少一个容器具有内存或 CPU 请求。 1 2 3 4 5 6 7 8 9 10 11 12 containers: - image: nginx:1.22.1 name: nginx resources: limits: cpu: 200m memory: 100Mi requests: cpu: 100m memory: 50Mi [root@master-01 resource]# kubectl describe pods ngx-dep-res-q2-56fcf66894-pwxf8 QoS Class: Burstable BestEffort 的 Pod 没有设置内存和 CPU 限制或请求 1 2 3 4 5 containers: - image: nginx:1.22.1 name: nginx [root@master-01 resource]# kubectl describe pods ngx-dep-res-q3-8d9b8b54c-rcq7t QoS Class: BestEffort Kubernetes 资源超卖 Kubernetes 在计算资源时是使用 request 字段进行计算的。 一个 worker 节点如果有 10 个 CPU 的资源。 Pod A 申请 request：5，limit：5， Pod B 申请 request 5，limit 5，是可以申请成功的。 而如果再启动一个 Pod C 申请 request 5， limit 5， 就会失败（Pod 处于 pending 状态， 一直在等待资源）。 因为 kubernetes 并不是按 limit 字段来计算资源用量而是使用 request 字段进行计算。\n上面的资源申请方式在 Kubernetes 就被称作 资源超卖。\n资源超卖 的意思就是说本来系统只有 10 个 CPU 的资源， 但是容器 A、B、C、D 都各自需要申请 5 个 CPU 的资源，这明显不够用。 但是如果 A、B、C、D 不可能在同一时刻都占满 5 个 CPU 资源， 因为每个服务都有它业务的高峰期和低谷期的。 高峰期的时候可以占满 5 个 CPU， 但是服务大部分时间都处于低谷期，可能只占用 1，2 个 CPU。 所以如果直接写 request:5 的话，很多时候资源是浪费的（ kubernetes 里即便容器没有使用到那么多资源， 也会为容器预留 request 字段的资源）。 所以我们可以为容器申请这样的资源： request：1， limit：5。 这样上面 4 个容器加起来只申请了 4 个 CPU 的资源， 而系统里有 10 个 CPU， 是完全可以申请到的。 而每个容器的 limit 又设置成了 5， 所以每个容器又都可以去使用 5 个 CPU 资源。\n容器状态探针 什么是容器探针 运行在Kubernetes中的应用需要用什么手段来检查应用的健康状态呢？\nKubernetes 用来判断应用是否“健康”的功能被命名为“探针”（Probe），也可以叫“探测器” 。\nKubernetes 为检查应用状态定义了三种探针，它们分别对应容器不同的状态：\nStartup，启动探针，用来检查应用是否已经启动成功，适合那些有大量初始化工作要做，启动很慢的应用。 Liveness，存活探针，用来检查应用是否正常运行，是否存在死锁、死循环。 Readiness，就绪探针，用来检查应用是否可以接收流量，是否能够对外提供服务 需要注意这三种探针是递进的关系：应用程序先启动，加载完配置文件等基本的初始化数据就进入了 Startup 状态，之后如果没有什么异常就是 Liveness 存活状态，但可能有一些准备作没有完成，还不一定能对外提供服务，只有到最后的 Readiness 状态才是一个容器最健康可用的状态。\nKubernetes 在启动容器后就会不断地调用探针来检查容器的状态：\n如果 Startup 探针失败，Kubernetes 会认为容器没有正常启动，就会尝试反复重启，当然其后面的 Liveness 探针和 Readiness 探针也不会启动。 如果 Liveness 探针失败，Kubernetes 就会认为容器发生了异常，也会重启容器。 如果 Readiness 探针失败，Kubernetes 会认为容器虽然在运行，但内部有错误，不能正常提供服务，就会把容器从 Service 对象的负载均衡集合中排除，不会给它分配流量。 使用容器探针 startupProbe、livenessProbe、readinessProbe 这三种探针的配置方式都是一样的，关键字段有这么几个：\nperiodSeconds，执行探测动作的时间间隔，默认是 10 秒探测一次。 timeoutSeconds，探测动作的超时时间，如果超时就认为探测失败，默认是 1 秒。 successThreshold，连续几次探测成功才认为是正常，对于 startupProbe 和livenessProbe 来说它只能是 1。 ​ 成功阈值\nfailureThreshold，连续探测失败几次才认为是真正发生了异常，默认是 3 次。 失败阈值 initialDelaySeconds： 初始化延迟时间 探测方式，Kubernetes 支持 3 种：Shell、TCP Socket、HTTP GET，它们也需要在探针里配置：\nexec，执行一个 Linux 命令，比如 ps、cat 等等，和 container 的 command 字段很类似。 tcpSocket，使用 TCP 协议尝试连接容器的指定端口。 Telnet ip port httpGet，连接端口并发送 HTTP GET 请求。 Curl http://ip:port 我们看一个使用探针的例子，还是以 Nginx 作为示例，用 ConfigMap 编写一个配置文件：\nnginx-conf-cm.yaml\n1 2 3 4 5 6 7 8 9 10 11 12 apiVersion: v1 kind: ConfigMap metadata: name: ngx-conf data: default.conf: | server { listen 80; location = /ready { return 200 \u0026#39;I am OK!\u0026#39;; } } nginx-dep-probe.yaml\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 apiVersion: apps/v1 kind: Deployment metadata: labels: app: ngx-dep-prob name: ngx-dep-prob spec: replicas: 1 selector: matchLabels: app: ngx-dep-prob template: metadata: labels: app: ngx-dep-prob spec: volumes: - name: ngx-conf-vol configMap: me: ngx-conf containers: - image: nginx:1.22.1 name: nginx ports: - containerPort: 80 volumeMounts: - mountPath: /etc/nginx/conf.d name: ngx-conf-vol startupProbe: # 启动探针 periodSeconds: 2 exec: command: [\u0026#34;cat\u0026#34;, \u0026#34;/var/run/nginx.pid\u0026#34;] livenessProbe: # 存活探针 periodSeconds: 5 tcpSocket: port: 80 readinessProbe: # 就绪探针 periodSeconds: 5 httpGet: path: /ready port: 80 StartupProbe 使用了 Shell 方式，使用 cat 命令检查 Nginx 存在磁盘上的进程号文件（/var/run/nginx.pid），如果存在就认为是启动成功，它的执行频率是每2秒探测一次。 LivenessProbe 使用了 TCP Socket 方式，尝试连接 Nginx 的 80 端口，每 5 秒探测一次。 ReadinessProbe 使用的是 HTTP GET 方式，访问容器的 /ready 路径，每 5 秒发一次请求。 我们使用 kubectl apply 创建 nginx deployment\n1 2 kubectl apply -f nginx-conf-cm.yaml kubectl apply -f nginx-dep-probe.yaml 观察 pod 状态\n使用 kubectl logs 命令查看 nginx 日志，可以看到探针的执行情况\n通过上图可以看到 Kubernetes 正是以大约 5 秒一次的频率，向 URI /ready 发送 HTTP 请求，不断地检查容器是否处于就绪状态。\n为了验证另两个探针的工作情况，我们可以修改探针，比如把命令改成检查错误的文件、错误的端口号：\n1 2 3 4 5 6 7 8 9 startupProbe: periodSeconds: 2 exec: command: [\u0026#34;cat\u0026#34;, \u0026#34;nginx.pid\u0026#34;] # 修改为错误的文件位置 livenessProbe: periodSeconds: 10 tcpSocket: port: 8080 # 修改为错误的端口 当 StartupProbe 探测失败的时候，Kubernetes 就会不停地重启容器，现象就是 RESTARTS 次数不停地增加，而 livenessProbe 和 readinessProbePod 没有执行，Pod 虽然是 Running 状态，也永远不会 READY：\n测试 Liveness 和 readinessProbe 执行频率 1 2 3 4 5 6 7 8 9 10 livenessProbe: periodSeconds: 10 httpGet: path: /live port: 80 readinessProbe: periodSeconds: 5 httpGet: path: /ready port: 80 ReplicaSet 控制器 Kubernetes 中的 ReplicaSet 主要的作用是维持一组 Pod 副本的运行，它的主要作用就是保证一定数量的 Pod 能够在集群中正常运行，它会持续监听这些 Pod 的运行状态，在 Pod 发生故障重启数量减少时重新运行新的 Pod 副本。\nnginx-reps.yaml\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 apiVersion: apps/v1 kind: ReplicaSet metadata: name: nginx-replicaset labels: app: nginx-reps spec: replicas: 3 selector: matchLabels: app: nginx-reps template: metadata: labels: app: nginx-reps spec: containers: - name: nginx image: nginx:1.22.1 Deployment 是一个可以拥有 ReplicaSet 并使用声明式方式在服务器端完成对 Pod 滚动更新的对象。 尽管 ReplicaSet 可以独立使用，目前它们的主要用途是提供给 Deployment 作为编排 Pod 创建、删除和更新的一种机制。当使用 Deployment 时，你不必关心如何管理它所创建的 ReplicaSet，Deployment 拥有并管理其ReplicaSet。 因此，建议你在需要 ReplicaSet 时使用 Deployment。\nReplicationController 控制器 ","date":"2026-04-06T17:11:59+08:00","image":"https://cdn.jsdelivr.net/gh/luckycloveryh/picgo-bed@main/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260524195303_534_12.jpg","permalink":"https://luckycloveryh.github.io/fuyou/p/k8s-deployment-high-availability/","title":"08-Deployment - 让应用永不宕机"},{"content":" Kubernetes 里专门用来管理配置信息的两种对象：ConfigMap 和 Secret，使用它们来灵活地配置、定制我们的应用。\nConfigMap/Secret 首先你要知道，应用程序有很多类别的配置信息，但从数据安全的角度来看可以分成两类：\n一类是明文配置，也就是不保密，可以任意查询修改，比如服务端口、运行参数、文件路径等等。 另一类则是机密配置，由于涉及敏感信息需要保密，不能随便查看，比如密码、密钥、证书等等。 这两类配置信息本质上都是字符串，只是由于安全性的原因，在存放和使用方面有些差异，所以 Kubernetes 也就定义了两个 API 对象，ConfigMap 用来保存明文配置，Secret 用来保存秘密配置\n什么是 ConfigMap 先来看 ConfigMap，我们仍然可以用命令 kubectl create 来创建一个它的 YAML 样板。注意，它有简写名字“cm”，所以命令行里没必要写出它的全称：\n1 kubectl create cm info --dry-run=client -o yaml 得到的模板文件大概是这个样子：\n1 2 3 4 5 apiVersion: v1 kind: ConfigMap metadata: creationTimestamp: null name: info ConfigMap 的 YAML 和之前我们学过的 Pod、Job 不一样，除了熟悉的“apiVersion”“kind”“metadata”，居然就没有其他的了，最重要的字段“spec”哪里去了？这是因为 ConfigMap 存储的是配置数据，是静态的字符串，并不是容器，所以它们就不需要用“spec”字段来说明运行时的“规格”。\n既然 ConfigMap 要存储数据，我们就需要用另一个含义更明确的字段“data”。\n要生成带有“data”字段的 YAML 样板，你需要在 kubectl create 后面多加一个参数 \u0026ndash;from-literal ，表示从字面值生成一些数据：\n1 kubectl create cm info --from-literal=k=v --dry-run=client -o yaml 注意，因为在 ConfigMap 里的数据都是 Key-Value 结构，所以 \u0026ndash;from-literal 参数需要使用 k=v 的形式。\n把 YAML 样板文件修改一下，再多增添一些 Key-Value，就得到了一个比较完整的 ConfigMap 对象：\n1 2 3 4 5 6 7 8 9 10 11 apiVersion: v1 kind: ConfigMap metadata: name: info data: count: \u0026#39;10\u0026#39; debug: \u0026#39;on\u0026#39; path: \u0026#39;/etc/systemd\u0026#39; greeting: | say hello to kubernetes. say hi to the world. 现在就可以使用 kubectl apply 把这个 YAML 交给 Kubernetes，让它创建 ConfigMap 对象了：\n1 kubectl apply -f cm.yaml 创建成功后，我们还是可以用 kubectl get、kubectl describe 来查看 ConfigMap 的状态：\n1 2 kubectl get cm kubectl describe cm info 你可以看到，现在 ConfigMap 的 Key-Value 信息就已经存入了 etcd 数据库，后续就可以被其他 API 对象使用。\n创建 ConfigMap 使用字面值创建 1 kubectl create configmap cm-conf --from-literal=debug=on --from-literal=count=10 从目录创建 1 2 3 kubectl create configmap nginx-config --from-file=nginx-conf kubectl get cm nginx-config kubectl describe cm nginx-config 从文件创建 1 kubectl create configmap nginx-config-2 --from-file=nginx-conf/default.conf 什么是 Secret 了解了 ConfigMap 对象，我们再来看 Secret 对象就会容易很多，它和 ConfigMap 的结构和用法很类似，不过在 Kubernetes 里 Secret 对象又细分出很多类，比如 :\nSecret 有四种类型：\nService Account Token ：用来访问 Kubernetes API，由 Kubernetes 自动创建，并且会自动挂载到 Pod 的 /run/secrets/kubernetes.io/serviceaccount 目录中； Opaque [oʊˈpeɪk]：base64 编码格式的 Secret，用来存储密码、密钥等； key: value kubernetes.io/dockerconfigjson ：用来存储私有 docker registry 的认证信息。 TLS**（传输层安全）**：用于存储 TLS 证书和私钥，以支持加密通信。它可以用于需要TLS证书的场景。 Https 对应具体有用途如下：\n身份识别的凭证信息 一般的机密信息（格式由用户自行解释） 访问私有镜像仓库的认证信息 HTTPS 通信的证书和私钥 我们先创建 Opaque 类型的，创建 YAML 样板的命令是 kubectl create secret generic ，同样，也要使用参数\n--from-literal 给出 Key-Value 值：\n1 kubectl create secret generic user --from-literal=username=root --dry-run=client -o yaml 得到的 Secret 对象大概是这个样子：\n1 2 3 4 5 6 7 apiVersion: v1 kind: Secret metadata: name: user data: username: cm9vdA== Secret 对象第一眼的感觉和 ConfigMap 非常相似，只是“kind”字段由“ConfigMap”变成了“Secret”，后面同样也是“data”字段，里面也是 Key-Value 的数据。\n这串“乱码”就是 Secret 与 ConfigMap 的不同之处，不让用户直接看到原始数据，起到一定的保密作用。不过它的手法非常简单，只是做了 Base64 编码，根本算不上真正的加密，我们可以直接使用 Linux 命令\u0026quot;base64\u0026quot; 来解密 ：\n1 2 3 4 5 6 7 8 # base64 解码 [root@k8s cm-sc]# echo \u0026#34;cm9vdA==\u0026#34; | base64 -d root # base64 编码 注意要加 -n 参数 [root@k8s cm-sc]# echo -n \u0026#34;mysql\u0026#34; | base64 bXlzcWw= [root@k8s cm-sc]# echo -n \u0026#34;123456\u0026#34; | base64 MTIzNDU2 我们再来重新编辑 Secret 的 YAML，为它添加两个新的数据，方式可以是参数 --from-literal 自动编码，也可以是自己手动编码：\n1 2 3 4 5 6 7 8 9 apiVersion: v1 kind: Secret metadata: name: user data: username: cm9vdA== password: MTIzNDU2 db: bXlzcWw= 使用 kubectl apply、kubectl get、kubectl describe 创建和查看对象：\n1 2 3 kubectl apply -f secret.yaml kubectl get secrets kubectl describe secrets user 这样一个存储敏感信息的 Secret 对象也就创建好了，而且因为它是保密的，使用 kubectl describe 不能直接看到内容，只能看到数据的大小，你可以和 ConfigMap 对比一下。\n创建Secret docker registry 认证的 secret 1 2 3 4 5 6 7 8 9 10 11 kubectl create secret docker-registry my-registry-secret --docker-server=reg.xxhf.cc --docker-username=admin --docker-password=password --docker-email=chijj@xxhf.cn apiVersion: v1 kind: Pod metadata: name: foo spec: imagePullSecrets: # 镜像拉取 secret - name: myregistrykey # secret-name containers: - name: foo image: reg.xinxianghf/lib/myapp:v1 TLS secret 如何使用 通过编写 YAML 文件，我们创建了 ConfigMap 和 Secret 对象，该怎么在 Kubernetes 里应用它们呢？\n因为 ConfigMap 和 Secret 只是一些存储在 etcd 里的字符串，所以如果想要在运行时产生效果，就必须要以某种方式“注入”到 Pod 里，让应用去读取。在这方面的处理上 Kubernetes 和Docker 是一样的，也是两种途径：环境变量和加载文件。\n先看比较简单的环境变量。\n以环境变量的方式 “valueFrom”字段指定了环境变量值的来源，可以是“configMapKeyRef”或者“secretKeyRef”，然后你要再进一步指定应用的 ConfigMap/Secret 的“name”和它里面的“key”。\n由于“valueFrom”字段在 YAML 里的嵌套层次比较深，初次使用最好看一下 kubectl explain 对它的说明：\n1 kubectl explain pod.spec.containers.env.valueFrom 下面是 引用了 ConfigMap 和 Secret 对象的 Pod 定义\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 apiVersion: v1 kind: Pod metadata: name: env-pod labels: owner: xxhf env: dev spec: containers: - name: busy image: busybox:latest imagePullPolicy: IfNotPresent command: [\u0026#34;/bin/sleep\u0026#34;, \u0026#34;3000\u0026#34;] env: - name: COUNT # 环境变量名字 valueFrom: configMapKeyRef: name: info # configmap 名字 key: count # info configmap 中定义 count key - name: DEBUG valueFrom: configMapKeyRef: name: info key: debug - name: USERNAME valueFrom: secretKeyRef: name: user # secret 名字 key: username # user secset 中的 username key - name: PASSWORD valueFrom: secretKeyRef: name: user key: password 用 kubectl apply 创建 Pod，再用 kubectl exec 进入 Pod，验证环境变量是否生效：\n1 2 3 4 5 6 7 kubectl apply -f env-pod.yaml kubectl exec -it env-pod -- sh echo $COUNT echo $DEBUG echo $USERNAME echo $PASSWORD 这张截图就显示了 Pod 的运行结果，可以看到在 Pod 里使用 echo 命令确实输出了我们在两个 YAML 里定义的配置信息，也就证明 Pod 对象成功组合了 ConfigMap 和 Secret 对象。\n以环境变量的方式使用 ConfigMap/Secret 还是比较简单的，下面来看第二种加载文件的方式。\n以 Volume 的方式 Kubernetes 为 Pod 定义了一个“Volume”的概念，可以翻译成是“存储卷”。如果把 Pod 理解成是一个虚拟机，那么 Volume 就相当于是虚拟机里的磁盘。\n注意： Volume 属于 Pod，不属于容器，所以它和字段“containers”是同级的，都属于“spec”。\n下面让我们来定义两个 Volume，分别引用 ConfigMap 和 Secret，名字是 cm-vol 和 sec-vol：\n1 2 3 4 5 6 7 8 spec: volumes: - name: cm-vol # 卷名字 configMap: name: info - name: sec-vol secret: secretName: user 使用 “volumeMounts”字段来挂载卷，可以把定义好的 Volume 挂载到容器里的某个路径下，所以需要在里面用“mountPath” “name”明确地指定挂载路径和 Volume 的名字。\n1 2 3 4 5 6 7 8 9 10 containers: - name: busy image: busybox:latest imagePullPolicy: IfNotPresent command: [\u0026#34;/bin/sleep\u0026#34;, \u0026#34;3000\u0026#34;] volumeMounts: - mountPath: /tmp/configmap # 挂载点 name: cm-vol # volume 的名字 - mountPath: /tmp/secret name: sec-vol 把“volumes”和“volumeMounts”字段都写好之后，配置信息就可以加载成文件了。\n下面是 Pod 的完整 YAML 描述，然后使用 kubectl apply 创建它.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 apiVersion: v1 kind: Pod metadata: name: env-pod labels: owner: xxhf env: dev spec: volumes: - name: cm-vol configMap: name: info - name: sec-vol secret: secretName: user containers: - name: busy image: busybox:latest imagePullPolicy: IfNotPresent command: [\u0026#34;/bin/sleep\u0026#34;, \u0026#34;3000\u0026#34;] volumeMounts: - mountPath: /tmp/configmap name: cm-vol - mountPath: /tmp/secret name: sec-vol 创建之后，我们还是用 kubectl exec 进入 Pod，看看配置信息被加载成了什么形式：\n1 2 3 kubectl apply -f vol-pod.yaml kubectl get pod kubectl exec -it env-pod -- sh ConfigMap 和 Secret 都变成了目录的形式，而它们里面的 Key-Value 变成了一个个的文件，而文件名就是 Key。\n因为这种形式上的差异，以 Volume 的方式来使用 ConfigMap/Secret，就和环境变量不太一样。\n环境变量用法简单，更适合存放简短的字符串，而 Volume 更适合存放大数据量的配置文件，在 Pod 里加载成文件后让应用直接读取使用。\n如果配置文件发生变化，Pod 中加载的数据是否会同步更新 ？ 热更新 以环境变量方式挂载 不支持热更新 以 volume 方式 挂载 支持热更新 总结 Kubernetes 里管理配置信息的 API 对象 ConfigMap 和 Secret，它们分别代表了明文信息和机密敏感信息，存储在 etcd 里，在需要的时候可以注入Pod 供 Pod 使用。\nConfigMap 记录了一些 Key-Value 格式的字符串数据，描述字段是“data”，不是“spec”。 Secret 与 ConfigMap 很类似，也使用“data”保存字符串数据，但它要求数据必须是 Base64 编码，起到一定的保密效果。 在 Pod 的 “env.valueFrom” 字段中可以引用 ConfigMap 和 Secret，把它们变成应用可以访 问的环境变量。 在 Pod 的 “spec.volumes” 字段中可以引用 ConfigMap 和 Secret，把它们变成存储卷，然后 在 “spec.containers.volumeMounts” 字段中加载成文件的形式。 ConfigMap 和 Secret 对存储数据的大小没有限制，但小数据用环境变量比较适合，大数据 应该用存储卷，可根据具体场景灵活应用。 1MB etcd ","date":"2026-04-06T17:08:58+08:00","image":"https://cdn.jsdelivr.net/gh/luckycloveryh/picgo-bed@main/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260524195301_533_12.jpg","permalink":"https://luckycloveryh.github.io/fuyou/p/k8s-configmap-secret-config/","title":"06-ConfigMap 和 Secret - 应用配置文件"},{"content":" “离线业务”类型的应用一般不直接服务于外部用户，只对内部用户有意义，比如日志分析、数据建模、视频转码等等，虽然计算量很大，但只会运行一段时间。“离线业务”的特点是必定会退出，不会无期限地运行下去，所以它的调度策略也就与“在线业务”存在很大的不同，需要考虑运行超时、状态检查、失败重试、获取计算结果等管理事项。\n“离线业务”可以分为两种。一种是“临时任务”，跑完就完事了，下次有需求了说一声再重新安排；另一种是“定时任务”，可以按时按点周期运行，不需要过多干预。\n对应到 Kubernetes 里，“临时任务”就是 API 对象 Job，“定时任务”就是 API 对象 CronJob，使用这两个对象你就能够在 Kubernetes 里调度管理任意的离线业务了。\n使用 YAML 描述 Job Job 的 YAML“文件头”部分还是那几个必备字段：\napiVersion 不是 v1，而是 batch/v1。 kind 是 Job，这个和对象的名字是一致的。 metadata 里仍然要有 name 标记名字，也可以用 labels 添加任意的标签。 使用以下命令可以生成一下 job 对象模板\n比如用 busybox 创建一个“echo-job”，命令就是这样的：\n1 2 # 生成 job YAML模板 kubectl create job echo-job --image=busybox:latest --dry-run=client -o yaml 会生成一个基本的 YAML 文件，保存之后做点修改，就有了一个 Job 对象：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 apiVersion: batch/v1 kind: Job metadata: name: echo-job spec: template: # Pod 的模板文件 spec: restartPolicy: Never containers: - name: echo-job image: busybox:latest imagePullPolicy: IfNotPresent command: [\u0026#34;/bin/echo\u0026#34;] args: [\u0026#34;hello\u0026#34;, \u0026#34;world\u0026#34;] 你会注意到 Job 的描述与 Pod 很像，但又有些不一样，主要的区别就在“spec”字段里，多了一个 template 字段，然后又是一个“spec”，显得有点怪。\n为了辅助你理解，我把 Job 对象重新组织了一下，用不同的颜色来区分字段，这样你就能够很容易看出来，其实这个“echo-job”里并没有太多额外的功能，只是把 Pod 做了个简单的包装：\n不过，因为 Job 业务的特殊性，所以我们还要在 spec 里多加一个字段 restartPolicy，确定 Pod 运行失败时的策略，OnFailure 是失败原地重启容器，而 Never 则是不重启容器，让 Job 去重新调度生成一个新的 Pod。\n创建 Job 现在让我们来创建 Job 对象，运行这个简单的离线作业，用的命令还是 kubectl apply：\n1 kubectl apply -f job.yaml 创建之后 Kubernetes 就会从 YAML 的模板定义中提取 Pod，在 Job 的控制下运行 Pod，你可以用 kubectl get job、kubectl get pod 来分别查看 Job 和 Pod 的状态：\n1 2 kubectl get job kubectl get pod 显示为 Completed 表示任务完成，而 Job 里也会列出运行成功的作业数量，这里只有一个作业，所以就是 1/1。\n你还可以看到，Pod 被自动关联了一个名字，用的是 Job 的名字（echo-job）再加上一个随机字符串（4w9fj），这当然也是 Job 管理的“功劳”，免去了我们手工定义的麻烦，这样我们就可以使用命令 kubectl logs 来获取 Pod 的运行结果：\n这里列出几个控制离线作业的重要字段，其他更详细的信息可以参考 Job 文档：\nactiveDeadlineSeconds，设置 job 运行的超时时间。 backoffLimit，设置 Pod 的失败重试次数。 completions，Job 完成需要运行多少个 Pod，默认是 1 个。 parallelism，它与 completions 相关，表示允许并发运行的 Pod 数量，避免过多占用资源。 要注意这 4 个字段是属于 Job 级别的，用来控制模板里的 Pod 对象。\n下面我再创建一个 Job 对象，名字叫“sleep-job”，它随机睡眠一段时间再退出，模拟运行时间较长的作业。Job 的参数设置成 15 秒超时，最多重试 2 次，总共需要运行完 4 个 Pod，但同一时刻最多并发 2 个 Pod：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 apiVersion: batch/v1 kind: Job metadata: name: sleep-job spec: activeDeadlineSeconds: 15 backoffLimit: 2 completions: 4 parallelism: 2 template: spec: restartPolicy: OnFailure containers: - name: echo-job image: busybox:latest imagePullPolicy: IfNotPresent command: - sh - -c - sleep $(($RANDOM % 10 + 1)) \u0026amp;\u0026amp; echo done 使用 kubectl apply 创建 Job 之后，我们可以用 kubectl get pod -w 来实时观察 Pod 的状态，看到 Pod 不断被排队、创建、运行的过程：\n1 2 kubectl apply -f sleep-job.yaml kubectl get pod -w 等到 4 个 Pod 都运行完毕，我们再用 kubectl get 来看看 Job 和 Pod 的状态：\n就会看到 Job 的完成数量如同我们预期的是 4，而 4 个 Pod 也都是完成状态。\n显然，“声明式”的 Job 对象让离线业务的描述变得非常直观，简单的几个字段就可以很好地控制作业的并行度和完成数量，不需要我们去人工监控干预，Kubernetes 把这些都自动化实现了。\n使用 YAML 描述 CronJob 使用命令 kubectl create 来创建 CronJob 的样板。\n要注意两点。第一，因为 CronJob 的名字有点长，所以 Kubernetes 提供了简写 cj，这个简写也可以使用命令 kubectl api-resources 看到；第二，CronJob 需要定时运行，所以我们在命令行里还需要指定参数 \u0026ndash;schedule。\n1 kubectl create cronjob echo-cj --image=busybox:latest --schedule=\u0026#34;*/2 * * * *\u0026#34; --dry-run=client -o yaml 然后我们编辑这个 YAML 样板，生成 CronJob 对象：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 apiVersion: batch/v1 kind: CronJob metadata: name: echo-cj spec: schedule: \u0026#34;*/1 * * * *\u0026#34; jobTemplate: spec: template: spec: restartPolicy: OnFailure containers: - name: echo-cj image: busybox:latest imagePullPolicy: IfNotPresent command: [\u0026#34;/bin/echo\u0026#34;] args: [\u0026#34;hello\u0026#34;, \u0026#34;world\u0026#34;] 我们还是重点关注它的 spec 字段，你会发现它居然连续有三个 spec 嵌套层次：\n第一个 spec 是 CronJob 自己的对象规格声明 第二个 spec 从属于“jobTemplate”，它定义了一个 Job 对象。 第三个 spec 从属于“template”，它定义了 Job 里运行的 Pod。 所以，CronJob 其实是又组合了 Job 而生成的新对象，我还是画了一张图，方便你理解它的“套娃”结构：\n除了定义 Job 对象的“jobTemplate”字段之外，CronJob 还有一个新字段就是“schedule”，用来定义任务周期运行的规则。它使用的是标准的 Cron 语法，指定分钟、小时、日、月、周，和 Linux 上的 crontab 是一样的。像在这里我就指定每分钟运行一次。\n除了名字不同，CronJob 和 Job 的用法几乎是一样的，使用 kubectl apply 创建 CronJob，使用 kubectl get cj、kubectl get pod 来查看状态：\n1 2 3 kubectl apply -f cj-job.yaml kubectl get cj kubectl get pod 总结 Kubernetes 设计哲学：单一职责，组合优于继承\n通过这种嵌套方式，Kubernetes 里的这些 API 对象就形成了一个“控制链”：CronJob 使用定时规则控制 Job，Job 使用并发数量控制 Pod，Pod 再定义参数控制容器，容器再隔离控制进程，进程最终实现业务功能，链条里的每个环节都各司其职，在 Kubernetes 的统一指挥下完成任务。\nPod 是 Kubernetes 的最小调度单元，但为了保持它的独立性，不应该向它添加多余的功能。 Kubernetes 为离线业务提供了 Job 和 CronJob 两种 API 对象，分别处理“临时任务”和“定时任务”。 Job 的关键字段是 spec.template，里面定义了用来运行业务的 Pod 模板，其他的重要字段有 completions、parallelism 等 CronJob 的关键字段是 spec.jobTemplate 和 spec.schedule，分别定义了 Job 模板和定时运行的规则 ","date":"2026-04-06T17:00:12+08:00","image":"https://cdn.jsdelivr.net/gh/luckycloveryh/picgo-bed@main/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260524195206_517_12.jpg","permalink":"https://luckycloveryh.github.io/fuyou/p/k8s-job-offline-task/","title":"05-Job：运行离线业务"},{"content":"介绍 Pod 为什么要有 Pod Pod 这个词原意是“豌豆荚”，后来又延伸出“舱室”“太空舱”等含义，你可以看一下这张图片，形象地来说 Pod 就是包含了很多组件、成员的一种结构。\n为了解决多应用联合运行的问题，同时还要不破坏容器的隔离，就需要在容器外面再建立一个**“收纳舱”**，让多个容器既保持相对独立，又能够小范围共享网络、存储等资源，而且永远是“绑在一起”的状态。\nPod 是 Kubernetes 的核心对象 因为 Pod 是对容器的“打包”，里面的容器是一个整体，总是能够一起调度、一起运行，绝不会出现分离的情况，而且 Pod 属于 Kubernetes，可以在不触碰下层容器的情况下任意定制修改。所以有了 Pod 这个抽象概念，Kubernetes 在集群级别上管理应用就会“得心应手”了。\nKubernetes 让 Pod 去编排处理容器，然后把 Pod 作为应用调度部署的最小单位，Pod 也因此成为了 Kubernetes 世界里的“原子”，基于Pod 就可以构建出更多更复杂的业务形态了。\n如何使用 YAML 描述 Pod 用命令 kubectl explain 来查看资源对象的详细说明，所以接下来我们一起看看写 YAML 时 Pod 里的一些常用字段。\n因为 Pod 也是 API 对象，所以它也必然具有 apiVersion、kind、metadata、spec 这四个基本组成部分。\n“apiVersion”和“kind”这两个字段很简单，对于 Pod 来说分别是固定的值 v1 和 Pod，而一般来说，“metadata”里应该有 name 和 labels 这两个字段。\n下面这段 YAML 代码就描述了一个简单的 Pod，名字是“busy-pod”，再附加上一些标签：\n1 2 3 4 5 6 7 8 9 apiVersion: v1 kind: Pod metadata: name: busy-pod labels: owner: xxhf env: dev region: beijing tier: back “metadata”一般写上 name 和 labels 就足够了，而“spec”字段由于需要管理、维护 Pod 这个Kubernetes 的基本调度单元，里面有非常多的关键信息。\n“containers”是一个数组，里面的每一个元素又是一个 container 对象，也就是容器。\n和 Pod 一样，container 对象也必须要有一个 name 表示名字，然后当然还要有一个 image 字段来说明它使用的镜像，这两个字段是必须要有的，否则 Kubernetes 会报告数据验证错误。\ncontainer 对象的其他字段基本上都可以和我们学过的 Docker、容器技术对应，理解起来难度不大，我们看几个较常用的：\nports：列出容器对外暴露的端口，和 Docker 的 -p 参数有点像。 imagePullPolicy：指定镜像的拉取策略，可以是 Always/Never/IfNotPresent，一般默认是 IfNotPresent，也就是说只有本地不存在才会远程拉取镜像，可以减少网络消耗。 env：定义 Pod 的环境变量，和 Dockerfile 里的 ENV 指令类似。 command：定义容器启动时要执行的命令，相当于 Dockerfile 里的 ENTRYPOINT 指令。 args：它是 command 运行时的参数，相当于 Dockerfile 里的 CMD 指令，这两个命令和Docker 的含义不同，要特别注意。 现在我们来编写“busy-pod”的 spec 部分，添加 env、command、args 等字段：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 spec: containers: - image: busybox:latest name: busy imagePullPolicy: IfNotPresent env: - name: name value: \u0026#34;xinxianghf\u0026#34; - name: address value: \u0026#34;beijing\u0026#34; command: - /bin/echo args: - \u0026#34;NAME=$(name), ADDRESS=$(address)\u0026#34; 这里我们为 Pod 指定使用镜像 busybox:latest，拉取策略是 IfNotPresent ，然后定义了 name 和 address 两个环境变量，启动命令是 /bin/echo，参数里输出刚才定义的环境变量。\n把这份 YAML 文件和 Docker 命令对比一下，你就可以看出，YAML 在 spec.containers 字段里用“声明式”把容器的运行状态描述得非常清晰准确，要比 docker run 那长长的命令行要整洁的多，对人、对机器都非常友好。\n完整的 YAML 描述：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 apiVersion: v1 kind: Pod metadata: name: busy-pod labels: owner: xxhf env: dev region: beijing tier: back spec: containers: - image: busybox:latest name: busy imagePullPolicy: IfNotPresent env: - name: name value: \u0026#34;xinxianghf\u0026#34; - name: address value: \u0026#34;beijing\u0026#34; command: - /bin/echo args: - \u0026#34;NAME=$(name), ADDRESS=$(address)\u0026#34; 如何使用 kubectl 操作 Pod 有了描述 Pod 的 YAML 文件，现在我们看一下用来操作 Pod 的 kubectl 命令。\nkubectl apply、kubectl delete 这两个命令可以使用-f参数指定 YAML 文件创建或者删除 Pod， 例如\n1 2 kubectl apply -f busy-pod.yml kubectl delete -f busy-pod.yml 不过，因为我们在 YAML 里定义了“name”字段，所以也可以在删除的时候直接指定名字来删除：\n1 kubectl delete pod busy-pod 和 Docker 不一样，Kubernetes 的 Pod 不会在前台运行，只能在后台（相当于默认使用了参数 -d)，所以输出信息不能直接看到。 我们可以用命令 kubectl logs，它会把 Pod 的标准输出流信息展示给我们，在这里就会显示出预设的两个环境变量的值：\n1 2 [root@master-01 04-pod]# kubectl logs busy-pod NAME=xinxianghf, ADDRESS=beijing 使用命令 kubectl get pod 可以查看 Pod 列表和运行状态：\n1 2 3 [root@master1 3.2]# kubectl get pod NAME READY STATUS RESTARTS AGE busy-pod 0/1 CrashLoopBackOff 6 (2m34s ago) 8m4s 你会发现这个 Pod 运行有点不正常，状态是“CrashLoopBackOff”，那么我们可以使用命令 kubectl describe 来检查它的详细状态，它在调试排错时很有用：\n1 kubectl describe pod busy-pod 通常需要关注的是末尾的“Events”部分，它显示的是 Pod 运行过程中的一些关键节点事件。对于这个 busy-pod，因为它只执行了一条 echo 命令就退出了，而 Kubernetes 默认会重启Pod，所以就会进入一个反复停止 - 启动的循环错误状态。\n因为 Kubernetes 里运行的应用大部分都是不会主动退出的服务，所以我们可以把这个 busypod 删掉，启动一个 Nginx 服务，这才是大多数 Pod 的工作方式。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 apiVersion: v1 kind: Pod metadata: name: nginx-pod labels: owner: xxhf env: dev spec: containers: - image: nginx:1.22.1 name: nginx imagePullPolicy: IfNotPresent ports: - containerPort: 80 kubectl apply -f nginx-pod.yml 启动之后，我们再用 kubectl get pod 来查看状态，就会发现它已经是“Running”状态了：\n命令 kubectl logs 也能够输出 Nginx 的运行日志：\n另外，kubectl 也提供与 docker 类似的 cp 和 exec 命令，kubectl cp 可以把本地文件拷贝进 Pod，kubectl exec 是进入 Pod 内部执行 Shell 命令，用法也差不多。\n比如我有一个“a.txt”文件，那么就可以使用 kubectl cp 拷贝进 Pod 的“/tmp”目录里：\n1 2 echo \u0026#34;demo\u0026#34; \u0026gt;\u0026gt; a.txt kubectl cp a.txt nginx-pod:/tmp 不过 kubectl exec 的命令格式与 Docker 有一点小差异，需要在 Pod 后面加上 \u0026ndash;，把kubectl 的命令与 Shell 命令分隔开，使用时需要注意一下：\n1 kubectl exec -it nginx-pod -- sh 使用标签组织 Pod 例如，对于微服务架构，部署的微服务数量可以轻松超过20个甚至更多。这些组件可能是副本（部署同一组件的多个副本）和多个不同的发布版本（stable、beta、canary等）同时运行。这样一来可能会导致我们在系统中拥有数百个pod，如果没有可以有效组织这些组件的机制，将会导致产生巨大的混乱。\n我们需要一种能够基于任意标准将pod组织成更小群体的方式，这样一来处理系统的每个开发人员和系统管理员都可以轻松地看到哪个pod是什么。此外，我们希望通过一次操作对属于某个组的所有pod进行操作，而不必单独为每个pod执行操作。\n通过标签来组织pod和所有其他Kubernetes对象。\n介绍标签 标签是一种简单却功能强大的Kubernetes特性，不仅可以组织pod，也可以组织所有其他的Kubernetes资源。详细来讲，标签是可以附加到资源的任意键值对，用以选择具有该确切标签的资源（这是通过标签选择器完成的）。只要标签的key在资源内是唯一的，一个资源便可以拥有多个标签。通常在我们创建资源时就会将标签附加到资源上，但之后我们也可以再添加其他标签，或者修改现有标签的值，而无须重新创建资源。\n通过给pod添加标签，可以得到一个更组织化的系统，以便我们理解。此时每个pod都标有两个标签：\napp，它指定pod属于哪个应用、组件或微服务。 env，它显示在pod中运行的环境是 dev、production 还是 qa 。 创建 pod 时指定标签 kubectl get pods命令默认不会列出任何标签，但我们可以使用\u0026ndash;showlabels选项来查看：\n1 kubectl get pod --show-labels 如果你只对某些标签感兴趣，可以使用 -L 选项指定它们并将它们分别显示在自己的列中，而不是列出所有标签。接下来我们再次列出所有pod，并将附加到pod 的标签列展示如下：\n1 2 3 4 5 6 [root@master-01 04-pod]# kubectl get pods -L env NAME READY STATUS RESTARTS AGE ENV busy-pod 0/1 CrashLoopBackOff 6 (48s ago) 6m34s dev nginx 1/1 Running 0 2m18s dev nginx-pod 1/1 Running 0 82s dev ngx-pro 1/1 Running 0 45s fat 修改现有pod的标签 标签也可以在现有pod上进行添加和修改。\n1 2 3 4 5 6 7 8 9 10 [root@master-01 04-pod]# kubectl get pods nginx-pod --show-labels NAME READY STATUS RESTARTS AGE LABELS nginx-pod 1/1 Running 0 3m26s env=dev,owner=xxhf [root@master-01 04-pod]# kubectl label pod nginx-pod tier=front pod/nginx-pod labeled [root@master-01 04-pod]# kubectl get pods nginx-pod --show-labels NAME READY STATUS RESTARTS AGE LABELS nginx-pod 1/1 Running 0 4m52s env=dev,owner=xxhf,tier=front 修改标签\n注意 在更改现有标签时，需要使用 --overwrite 选项。\n1 2 [root@master-01 04-pod]# kubectl label pod nginx-pod env=production --overwrite pod/nginx-pod labeled 再次列出pod以查看更新后的标签：\n1 2 3 [root@master-01 04-pod]# kubectl get pods nginx-pod --show-labels NAME READY STATUS RESTARTS AGE LABELS nginx-pod 1/1 Running 0 6m27s env=production,owner=xxhf,tier=front 标签选择运算符 目前支持两种类型的选择算符：基于等值的和基于集合的。\n基于等值 基于等值或基于不等值的需求允许按标签键和值进行过滤。 匹配对象必须满足所有指定的标签约束，尽管它们也可能具有其他标签。 可接受的运算符有 =、== 和 != 三种。 前两个表示相等（并且是同义词），而后者表示不相等。例如：\n1 2 environment = production tier != frontend 基于集合 基于集合的标签需求允许你通过一组值来过滤键。 支持三种操作符：in、notin 和 exists（只可以用在键标识符上）。例如：\n1 2 3 4 environment in (production, qa) tier notin (frontend, backend) partition !partition 按标签选择Pod 1 2 3 4 5 kubectl get pods -l tier=front kubectl get pods -l \u0026#39;environment in (production, qa)\u0026#39; kubectl get pods -l \u0026#39;environment,environment notin (frontend)\u0026#39; 使用命名空间组织 Pod ( namespace ) Kubernetes 的命名空间并不是一个实体对象，只是一个逻辑上的概念。它可以把集群切分成一个个彼此独立的区域，然后我们把对象放到这些区域里，就实现了类似容器技术里 namespace 的隔离效果，应用只能在自己的命名空间里分配资源和运行，不会干扰到其他命名空间里的应用。\n在一个 K8s 集群中，命名空间可以划分为两种类型：系统级命名空间和用户自定义命名空\n间。\n系统级的命名空间是 K8s 集群默认创建的命名空间，主要用来隔离系统级的对象和业务对\n象，系统级的命名空间下面四种。\ndefault：默认命名空间，也就是在不指定命名空间时的默认命名空间。 kube-system：K8s 系统级组件的命名空间，所有 K8s 的关键组件（例如 kube-proxy、 coredns、metric-server 等）都在这个命名空间下。\nkube-public：开放的命名空间，所有的用户都可以读取，这个命名空间是一个约定，但不是必须。 kube-node-lease：和集群扩展相关的命名空间。 在这几个命名空间中，除了 default 命名空间，你都不应该将业务应用部署在其他系统默认创建的命名空间下，也不要尝试去删除它们，这可能会导致集群异常。\n在一个 K8s 集群中，你可以使用 kubectl get ns 命令来查看 K8s 的命名空间：\n1 2 3 4 5 6 7 [root@node-01 ~]# kubectl get ns NAME STATUS AGE default Active 41d kube-node-lease Active 41d kube-public Active 41d kube-system Active 41d middleware Active 5d21h 创建命名空间\n1 kubectl create namespace dev-ns 获取命名空间详细信息\n1 2 3 4 5 6 7 8 9 [root@node-01 ~]# kubectl describe ns dev-ns Name: dev-ns Labels: kubernetes.io/metadata.name=dev-ns Annotations: \u0026lt;none\u0026gt; Status: Active No resource quota. No LimitRange resource. 将 Pod 运行在指定的命名空间中\n1 2 3 4 5 6 7 [root@master-01 04-pod]# kubectl -n dev-ns apply -f nginx-pod.yaml pod/nginx-pod created [root@master-01 04-pod]# [root@master-01 04-pod]# [root@master-01 04-pod]# kubectl -n dev-ns get pods NAME READY STATUS RESTARTS AGE nginx-pod 1/1 Running 0 6s Pod 停止和移除 按名称删除\n1 kubectl delete pod nginx-pod 使用标签选择器删除\n1 2 kubectl get pods --show-labels kubectl delete pods -l env=dev 删除命名空间所有 Pod\n1 kubectl delete namespace dev-ns 特殊类型的容器 Pause 容器 在Kubernetes中，pause 容器作为你的 pod 中所有容器的“父容器”。pause 容器有两个核心职责。首先，它是 pod 中Linux 名称空间共享的基础。其次，启用了PID(进程ID)命名空间共享后，它为每个pod充当 PID 1，并接收僵尸进程。\n当检查你的 Kubernetes 集群的节点时，在节点上执行容器查看命令，你可能会注意到一些被称为“暂停”（pause）的容器，例如：\n1 2 3 4 [root@master-01 ~]# nerdctl -n k8s.io ps | grep pause 37ed7c409708 registry.aliyuncs.com/google_containers/pause:3.8 \u0026#34;/pause\u0026#34; 20 minutes ago Up k8s://kube-system/coredns-668d6bf9bc-jj82q 5933b8af499d registry.aliyuncs.com/google_containers/pause:3.8 \u0026#34;/pause\u0026#34; 20 minutes ago Up k8s://kube-system/coredns-668d6bf9bc-7dx9p 24abd7734ae0 registry.aliyuncs.com/google_containers/pause:3.8 \u0026#34;/pause\u0026#34; 20 minutes ago Up k8s://kube-system/calico-kube-controllers-77969b7d87-skzk4 你会疑惑这些容器并不是你创建的。是的，这些容器是 Kubernetes”免费赠送“的。\nKubernetes 中的 pause 容器有时候也称为 infra 容器，它与用户容器”捆绑“运行在同一个 Pod 中，最大的作用是维护 Pod 网络协议栈。\n启动一个Pod 可以看到同时启动两个 docker 容器\n1 2 3 4 [root@worker-01 ~]# nerdctl -n k8s.io ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES d0a76bd3fd93 docker.io/library/nginx:1.22.1 \u0026#34;/docker-entrypoint.…\u0026#34; 5 seconds ago Up k8s://default/nginx-pod/nginx d0e49f1fa6a6 registry.aliyuncs.com/google_containers/pause:3.8 \u0026#34;/pause\u0026#34; 14 seconds ago Up k8s://default/nginx-pod 使用 nsenter -t 22376 -n ip a 可以看到两个容器是共享同一个 network 命名空间\n使用 ls -l /proc/22471/ns 命令查看每个容器的 Namespace，可以看到 Pod 内是共享 net 和 ipc namespace 的。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 lrwxrwxrwx 1 root root 0 Nov 2 10:21 ipc -\u0026gt; ipc:[4026532262] lrwxrwxrwx 1 root root 0 Nov 2 10:21 mnt -\u0026gt; mnt:[4026532332] lrwxrwxrwx 1 root root 0 Nov 2 10:21 net -\u0026gt; net:[4026532265] lrwxrwxrwx 1 root root 0 Nov 2 10:21 pid -\u0026gt; pid:[4026532334] lrwxrwxrwx 1 root root 0 Nov 2 10:35 user -\u0026gt; user:[4026531837] lrwxrwxrwx 1 root root 0 Nov 2 10:21 uts -\u0026gt; uts:[4026532333] lrwxrwxrwx 1 65535 65535 0 Nov 2 10:21 ipc -\u0026gt; ipc:[4026532262] lrwxrwxrwx 1 65535 65535 0 Nov 2 10:35 mnt -\u0026gt; mnt:[4026532260] lrwxrwxrwx 1 65535 65535 0 Nov 2 10:21 net -\u0026gt; net:[4026532265] lrwxrwxrwx 1 65535 65535 0 Nov 2 10:35 pid -\u0026gt; pid:[4026532263] lrwxrwxrwx 1 65535 65535 0 Nov 2 10:35 user -\u0026gt; user:[4026531837] lrwxrwxrwx 1 65535 65535 0 Nov 2 10:35 uts -\u0026gt; uts:[4026532261] 演示 pause 容器用途\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # 1. 启动 pause 容器 nerdctl run -d --name pause \\ -p 8880:80 \\ --ipc=shareable \\ registry.aliyuncs.com/google_containers/pause:3.8 # 2. 启动 nginx 共享 pause 的网络和 IPC nerdctl run -d --name nginx-pause \\ --net=container:pause \\ --ipc=container:pause \\ --pid=container:pause \\ registry.cn-beijing.aliyuncs.com/xxhf/nginx:1.22.1 # 3. 启动 centos 共享 pause 的网络和 IPC nerdctl run -it --name rocky-pause \\ --net=container:pause \\ --ipc=container:pause \\ --pid=container:pause \\ registry.cn-beijing.aliyuncs.com/xxhf/rockylinux:9 sh Init Containers 什么是 init Containers Pod 能够具有多个容器，应用运行在容器里面，但是它也可能有一个或多个先于应用容器启动的 Init 容器。\nInit 容器与普通的容器非常像，除了如下两点：\nInit 容器总是运行到成功完成为止。 每个 Init 容器都必须在下一个 Init 容器启动之前成功完成。 如果 Pod 的 Init 容器失败，Kubernetes 会不断地重启该 Pod，直到 Init 容器成功为止。然而，如果 Pod 对应的 restartPolicy 为 Never，它不会重新启动。\nInit 容器能做什么？ 因为 Init 容器具有与应用程序容器分离的单独镜像，所以它们的启动相关代码具有如下优势：\n等待其他模块 Ready：这个可以用来解决服务之间的依赖问题，比如我们有一个 Web 服务，该服务又依赖于另外一个数据库服务，但是在我们启动这个 Web 服务的时候我们并不能保证依赖的这个数据库服务就已经启动起来了，所以可能会出现一段时间内 Web 服务连接数据库异常。要解决这个问题的话我们就可以在 Web 服务的 Pod 中使用一个InitContainer，在这个初始化容器中去检查数据库是否已经准备好了，准备好了过后初始化容器就结束退出，然后我们的主容器 Web 服务被启动起来，这个时候去连接数据库就不会有问题了。 做初始化配置：比如集群里检测所有已经存在的成员节点，为主容器准备好集群的配置信息，这样主容器起来后就能用这个配置信息加入集群。 其它场景：如将Pod注册到一个中央数据库、配置中心等。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 apiVersion: v1 kind: Pod metadata: name: myapp-pod labels: app.kubernetes.io/name: MyApp spec: containers: - name: myapp-container image: busybox:latest command: [\u0026#39;sh\u0026#39;, \u0026#39;-c\u0026#39;, \u0026#39;echo The app is running! \u0026amp;\u0026amp; sleep 3600\u0026#39;] initContainers: - name: init-myservice image: busybox:1.28 command: [\u0026#39;sh\u0026#39;, \u0026#39;-c\u0026#39;, \u0026#34;until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done\u0026#34;] - name: init-mydb image: busybox:1.28 command: [\u0026#39;sh\u0026#39;, \u0026#39;-c\u0026#39;, \u0026#34;until nslookup mydb.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for mydb; sleep 2; done\u0026#34;] 在 Pod 启动过程中，Init 容器会按顺序在网络和数据卷初始化之后启动。每个容器必须在下一个容器启动之前成功退出。如果由于运行时或失败退出，将导致容器启动失败，它会根据 Pod 的 restartPolicy 指定的策略进行重试。然而，如果 Pod 的 restartPolicy 设置为 Always，Init 容器失败时会使用 RestartPolicy 策略。\n在所有的 Init 容器没有成功之前，Pod 将不会变成 Ready 状态。正在初始化中的 Pod 处于 Pending 状态，但应该会将 Initializing 状态设置为 true。\n如果 Pod 重启，所有 Init 容器必须重新执行。\n对 Init 容器 spec 的修改被限制在容器 image 字段，修改其他字段都不会生效。更改 Init 容器的 image 字段，等价于重启该 Pod。\n因为 Init 容器可能会被 重启、重试或者重新执行，所以 Init 容器的代码应该是 幂等 的。\nPod 生命周期 Pod 阶段 Pod 的 status 字段是一个 PodStatus 对象，其中包含一个 phase 字段。\nPod 的阶段（Phase）是 Pod 在其生命周期中所处位置的简单概述。\n下面是 phase 可能的值：\nPod 遵循预定义的生命周期，起始于 Pending 阶段， 如果至少其中有一个主要容器正常启动，则进入 Running，之后取决于 Pod 中是否有容器以失败状态结束而进入 Succeeded 或者 Failed 阶段。\n在 Pod 运行期间，kubelet 能够重启容器以处理一些失效场景。 在 Pod 内部，Kubernetes 跟踪不同容器的状态并确定使 Pod 重新变得健康所需要采取的动作。\nPod 在其生命周期中只会被调度一次。 一旦 Pod 被调度（分派）到某个节点，Pod 会一直在该节点运行，直到Pod 停止或者被终止。\n下面是 phase 的可能取值：\n挂起（Pending）：Pod 信息已经提交给了集群，但是还没有被调度器调度到合适的节点或者 Pod 里的镜像正在下载 运行中（Running）：该 Pod 已经绑定到了一个节点上，Pod 中所有的容器都已被创建。至少有一个容器正在运行，或者正处于启动或重启状态 成功（Succeeded）：Pod 中的所有容器都被成功终止，并且不会再重启 失败（Failed）：Pod 中的所有容器都已终止了，并且至少有一个容器是因为失败终止。也就是说，容器以非0状态退出或者被系统终止 未知（Unknown）：因为某些原因无法取得 Pod 的状态，通常是因为与 Pod 所在主机通信失败导致的 容器状态 一旦调度器将 Pod 分派给某个节点，kubelet 就通过容器运行时开始为 Pod 创建容器。容器的状态有三种：Waiting（等待）、Running（运行中）和 Terminated（已终止）。\nWaiting （等待） 如果容器并不处在 Running 或 Terminated 状态之一，它就处在 Waiting 状态。 处于 Waiting 状态的容器仍在运行它完成启动所需要的操作：例如， 从某个容器镜像仓库拉取容器镜像，或者向容器应用 Secret 数据等等。 当你使用 kubectl 来查询包含 Waiting 状态的容器的 Pod 时，你也会看到一个 Reason 字段，其中给出了容器处于等待状态的原因。\nRunning（运行中） Running 状态表明容器正在执行状态并且没有问题发生。 如果配置了 postStart 回调，那么该回调已经执行且已完成。 如果你使用 kubectl 来查询包含 Running 状态的容器的 Pod 时， 你也会看到关于容器进入 Running 状态的信息。\nTerminated（已终止） 处于 Terminated 状态的容器已经开始执行并且或者正常结束或者因为某些原因失败。 如果你使用 kubectl 来查询包含 Terminated 状态的容器的 Pod 时， 你会看到容器进入此状态的原因、退出代码以及容器执行期间的起止时间。\n如果容器配置了 preStop 回调，则该回调会在容器进入 Terminated 状态之前执行。\n容器重启策略 Pod 的 spec 中包含一个 restartPolicy 字段，其可能取值包括 Always、OnFailure 和 Never。默认值是 Always。\nrestartPolicy 适用于 Pod 中的所有容器。restartPolicy 仅针对同一节点上 kubelet 的容器重启动作。当 Pod 中的容器退出时，kubelet 会按指数回退方式计算重启的延迟（10s、20s、40s、\u0026hellip;），其最长延迟为 5 分钟。\n容器生命周期回调(Hook) 钩子函数 有两个回调暴露给容器：\nPostStart 这个回调在容器被创建之后立即被执行。 但是，不能保证回调会在容器入口点（ENTRYPOINT）之前执行。\nPreStop # 优雅退出 在容器被终止之前，此回调会被调用。在容器终止之前调用，常用于在容器结束前优雅的释放资源。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 apiVersion: v1 kind: Pod metadata: name: lifecycle-demo spec: containers: - name: lifecycle-demo-container image: nginx:1.22.1 lifecycle: postStart: exec: command: [\u0026#34;/bin/sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;echo Hello from the postStart handler \u0026gt; /usr/share/message\u0026#34;] preStop: exec: command: [\u0026#34;/usr/sbin/nginx\u0026#34;,\u0026#34;-s\u0026#34;,\u0026#34;quit\u0026#34;] postStart 指的是，在容器启动后，立刻执行一个指定的操作。需要明确的是，postStart 定义的操作，虽然是在 Docker 容器 ENTRYPOINT 执行之后，但它并不严格保证顺序。也就是说，在 postStart 启动时，ENTRYPOINT 有可能还没有结束。当然，如果 postStart 执行超时或者错误，Kubernetes 会在该 Pod 的 Events 中报出该容器启动失败的错误信息，导致 Pod 也处于失败的状态。\npreStop 发生的时机，则是容器被杀死之前（比如，收到了 SIGKILL 信号）。而需要明确的是，preStop 操作的执行，是同步的。所以，它会阻塞当前的容器杀死流程，直到这个 Hook 定义操作完成之后，才允许容器被杀死，这跟 postStart 不一样。\n所以，在这个例子中，我们在容器成功启动之后，在 /usr/share/message 里写入了一句“欢迎信息”（即 postStart 定义的操作）。而在这个容器被删除之前，我们则先调用了 nginx 的退出指令（即 preStop 定义的操作），从而实现了容器的“优雅退出”。\n附录 Kubectl 命令补全 1 2 3 4 dnf -y install bash-completion echo \u0026#34;source \u0026lt;(kubectl completion bash)\u0026#34; \u0026gt;\u0026gt; ~/.bashrc source ~/.bashrc bash restartPolicy Pod通过restartPolicy字段指定重启策略，重启策略类型为：Always、OnFailure 和 Never，默认为 Always。\nrestartPolicy 仅指通过同一节点上的 kubelet 重新启动容器。\n重启策略 说明 Always 当容器失效时，由kubelet自动重启该容器， 默认 OnFailure 当容器终止运行且退出码不为0时，由kubelet自动重启该容器 Never 不论容器运行状态如何，kubelet都不会重启该容器 Nerdctl run 参数 1 2 3 4 5 6 7 --net=container:\u0026lt;container_name\u0026gt;：容器与另一个指定容器共享网络命名空间。这意味着两个容器共享相同的网络栈，可以直接通信。使用此模式时，两个容器可以看到彼此的网络接口和端口。 --ipc=container:\u0026lt;container_name\u0026gt;：容器与另一个指定容器共享IPC命名空间。这使得两个容器可以直接进行IPC通信，共享相同的IPC资源。例如，可以使用这个选项在容器之间共享内存段。 --pid=container:\u0026lt;container_name\u0026gt;：容器与另一个指定容器共享PID命名空间。这使得两个容器可以共享相同的进程命名空间，进程在两个容器之间是可见的。例如，可以使用此选项在一个容器中监视和管理另一个容器的进程。 1 kubectl set env daemonset/calico-node -n kube-system IP_AUTODETECTION_METHOD=interface=ens192 ","date":"2026-04-06T16:56:11+08:00","image":"https://cdn.jsdelivr.net/gh/luckycloveryh/picgo-bed@main/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260524195209_518_12.jpg","permalink":"https://luckycloveryh.github.io/fuyou/p/k8s-pod-core-concept/","title":"04-Pod：Kubernetes 里最核心的概念"},{"content":"Kubeadm 简介 为了简化 Kubernetes 的部署工作，让它能够更“接地气”，社区里就出现了一个专门用来在集群中安装 Kubernetes 的工具，名字就叫“kubeadm”，意思就是“Kubernetes 管理员”。\nKubeadm 是用容器和镜像来封装 Kubernetes 的各种组件，但它的目标不是单机部署，而是要能够轻松地在集群环境里部署 Kubernetes，并且让这个集群接近甚至达到生产级质量。\n1. 环境准备 ( Master 和 Worker 都 执行 ) 1.1 所需服务器 角色 主机名 IP 最小配置 建议配置 操作系统 Master 节点 master-01 10.206.0.15 2C4G 2C4G Rockylinux 9.4 Worker 节点 worker-01 10.206.0.9 2C2G 2C4G Rockylinux 9.4 所谓的多节点集群，要求服务器应该有两台或者更多，为了简化我们只取最小值，所以这个Kubernetes 集群就只有两台主机，一台是 Master 节点，另一台是 Worker 节点。当然，在完全掌握了 kubeadm 的用法之后，你可以在这个集群里添加更多的节点。 Master 节点需要运行 apiserver、etcd、scheduler、controller-manager 等组件，管理整个集群，所以对配置要求比较高，至少是 2 核 CPU、4GB 的内存。\n而 Worker 节点没有管理工作，只运行业务应用，所以配置可以低一些，为了节省资源可以给它分配 2 核 CPU 和 2GB 的内存。\n基于模拟生产环境的考虑，在 Kubernetes 集群之外还需要有一台起辅助作用的服务器。它的名字叫 Console，意思是控制台，我们要在上面安装命令行工具 kubectl，所有对Kubernetes 集群的管理命令都是从这台主机发出去的。这也比较符合实际情况，因为安全的原因，集群里的主机部署好之后应该尽量少直接登录上去操作。要提醒你的是，Console 这台主机只是逻辑上的概念，不一定要是独立，完全可以复用 Master/Worker 节点作为控制。\n1.2 配置主机名 主机名不是必须要修改，在真实环境中只要主机名不一样即可，为了学习时方便，还是建议修改。\n1 2 3 hostnamectl set-hostname master-01 hostnamectl set-hostname worker-01 1.3 关闭防火墙 1 2 # 关闭防火墙，并禁止开机自动运行 systemctl disable --now firewalld 1.4 关闭 SELinux 1 2 3 4 # 设置 SELinux 的执行模式。0 表示关闭 SELinux。 setenforce 0 # 修改 SELinux 配置文件 /etc/selinux/config ，禁用 SELinux sed -i \u0026#39;s#SELINUX=enforcing#SELINUX=disabled#g\u0026#39; /etc/selinux/config 1.5 关闭交换分区 1 swapoff -a; sed -i \u0026#39;/swap/d\u0026#39; /etc/fstab 1.6 配置时钟同步 保证所有的服务器的时间一致\n1 2 3 4 5 6 7 8 9 10 11 12 13 yum -y install chrony # 修改配置文件 /etc/chrony.conf ，添加如下行 server ntp.aliyun.com iburst server ntp1.aliyun.com iburst server ntp2.aliyun.com iburst # 启动服务 systemctl restart chronyd systemctl enable chronyd # 检查状态 chronyc tracking 1.7 修改内核参数 1 2 3 4 5 6 7 8 9 10 cat \u0026gt;\u0026gt;/etc/sysctl.d/kubernetes.conf\u0026lt;\u0026lt;EOF net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1 EOF sysctl --system # net.ipv4.ip_forward = 1 启用了IPv4的IP转发功能，允许服务器作为网络路由器转发数据包。 # net.bridge.bridge-nf-call-iptables = 1 当使用网络桥接技术时，将数据包传递到iptables进行处理。 1.8 配置 hosts 本地解析 1 2 3 4 5 6 7 cat \u0026gt; /etc/hosts \u0026lt;\u0026lt;EOF 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 10.7.0.15 master-01 10.7.4.13 worker-01 EOF 1.9 安装 容器运行时 Containerd k8s 从 v1.24 版本开始取消了对 Docker 容器运行时的支持，默认使用 Containerd，虽然这是k8s多年的心愿，但对于用户来说并没有益处，还需要额外学习 Containerd 相关的命令，神仙打架，殃及池鱼啊。\nContainerd 的二进制安装方式有点麻烦，我们就直接使用 docker 公司提供的 yum 仓库安装， containerd 是 Docker 公司捐献给 CNCF 的。\n安装 containerd\n1 2 3 4 5 6 7 dnf install -y yum-utils # 使用 阿里云仓库 dnf config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo #官方镜像 dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 安装 containerd dnf -y install containerd.io-1.7.29 修改 containerd 配置文件\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # 创建默认配置文件 cd /etc/containerd mv config.toml config.toml.orig containerd config default \u0026gt; /etc/containerd/config.toml # 修改 Containerd cgroup 配置 sed -i \u0026#34;s#SystemdCgroup\\ \\=\\ false#SystemdCgroup\\ \\=\\ true#g\u0026#34; /etc/containerd/config.toml # grep -i SystemdCgroup /etc/containerd/config.toml # SystemdCgroup = true # false 改为 true # 修改 sandbox 沙箱镜像地址（就是 pause 镜像地址），国外就不用修改 sed -i \u0026#34;s#registry.k8s.io#registry.aliyuncs.com/google_containers#g\u0026#34; /etc/containerd/config.toml # grep -i sandbox_image /etc/containerd/config.toml # sandbox_image = \u0026#34;registry.aliyuncs.com/google_containers/pause:3.8\u0026#34; # 修改镜像仓库 从 registry.k8s.io 修改为 阿里云 启动 containerd 服务\n1 2 3 systemctl enable --now containerd systemctl status containerd ctr version 二进制安装 containerd 的步骤大概是：\n安装 containerd 安装 runc 安装 CNI plugins 这个步骤可以帮助我们理解是容器是如何创建出来的，但初学者可以先忽略。\n1.10 安装 kubeadm 和 k8s 组件 k8s 官方的仓库访问受限，我们使用阿里云的仓库\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 cat \u0026lt;\u0026lt;EOF | tee /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.32/rpm/ enabled=1 gpgcheck=1 gpgkey=https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.32/rpm/repodata/repomd.xml.key EOF #官方仓库 cat \u0026lt;\u0026lt;EOF | sudo tee /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://pkgs.k8s.io/core:/stable:/v1.32/rpm/ enabled=1 gpgcheck=1 gpgkey=https://pkgs.k8s.io/core:/stable:/v1.32/rpm/repodata/repomd.xml.key exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni EOF #国外版仓库安装 dnf install -y kubeadm kubelet kubectl --disableexcludes=kubernetes 安装\n1 dnf install -y kubeadm-1.32.2 kubelet-1.32.2 kubectl-1.32.2 启动服务\n1 systemctl enable --now kubelet 2. Master 节点初始化 (只在 master 节点执行) 2.1 生成 kubeadm 初始化的配置文件 1 kubeadm config print init-defaults \u0026gt; kubeadm-config.yaml 2.2 修改配置文件 默认生成的kubeadm初始化配置文件 kubeadm-config.yaml 需要修改以下内容：\n1 2 3 4 5 6 7 8 9 10 11 12 localAPIEndpoint: advertiseAddress: 10.206.0.12 # 修改为 master节点IP地址，如果使用公有云，配置虚机的内网地址 bindPort: 6443 ... ... imageRepository: registry.aliyuncs.com/google_containers # 修改为阿里云镜像仓库 kind: ClusterConfiguration kubernetesVersion: 1.32.2 # # 需要安装的 k8s 版本号 networking: dnsDomain: cluster.local serviceSubnet: 10.96.0.0/12 podSubnet: 10.244.0.0/16 # 添加 pod 网络 CIDR 地址 完整的kubeadm-config.yaml 配置文件内容如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 apiVersion: kubeadm.k8s.io/v1beta4 bootstrapTokens: - groups: - system:bootstrappers:kubeadm:default-node-token token: abcdef.0123456789abcdef ttl: 24h0m0s usages: - signing - authentication kind: InitConfiguration localAPIEndpoint: advertiseAddress: 10.206.0.12 # 修改为 master节点IP地址，如果使用公有云，配置虚机的内网地址 bindPort: 6443 nodeRegistration: criSocket: unix:///var/run/containerd/containerd.sock imagePullPolicy: IfNotPresent imagePullSerial: true name: master-01 # master 节点主机名 taints: null timeouts: controlPlaneComponentHealthCheck: 4m0s discovery: 5m0s etcdAPICall: 2m0s kubeletHealthCheck: 4m0s kubernetesAPICall: 1m0s tlsBootstrap: 5m0s upgradeManifests: 5m0s --- apiServer: {} apiVersion: kubeadm.k8s.io/v1beta4 caCertificateValidityPeriod: 87600h0m0s certificateValidityPeriod: 8760h0m0s certificatesDir: /etc/kubernetes/pki clusterName: kubernetes controllerManager: {} dns: {} encryptionAlgorithm: RSA-2048 etcd: local: dataDir: /var/lib/etcd imageRepository: registry.aliyuncs.com/google_containers # 修改为阿里云镜像仓库 kind: ClusterConfiguration kubernetesVersion: 1.32.2 # 需要安装的 k8s 版本号 networking: dnsDomain: cluster.local serviceSubnet: 10.96.0.0/12 podSubnet: 10.244.0.0/16 # 添加 pod 网络 CIDR 地址 proxy: {} scheduler: {} 2.3 下载集群初始化所需镜像 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # 查看 Kubernetes 初始化需要用到的镜像 [root@master-01 ~]# kubeadm config --config=kubeadm-config.yaml images list registry.aliyuncs.com/google_containers/kube-apiserver:v1.32.2 registry.aliyuncs.com/google_containers/kube-controller-manager:v1.32.2 registry.aliyuncs.com/google_containers/kube-scheduler:v1.32.2 registry.aliyuncs.com/google_containers/kube-proxy:v1.32.2 registry.aliyuncs.com/google_containers/coredns:v1.11.3 registry.aliyuncs.com/google_containers/pause:3.10 registry.aliyuncs.com/google_containers/etcd:3.5.16-0 # 下载初始化需要用的镜像 [root@master-01 ~]# kubeadm config --config=kubeadm-config.yaml images pull [config/images] Pulled registry.aliyuncs.com/google_containers/kube-apiserver:v1.32.2 [config/images] Pulled registry.aliyuncs.com/google_containers/kube-controller-manager:v1.32.2 [config/images] Pulled registry.aliyuncs.com/google_containers/kube-scheduler:v1.32.2 [config/images] Pulled registry.aliyuncs.com/google_containers/kube-proxy:v1.32.2 [config/images] Pulled registry.aliyuncs.com/google_containers/coredns:v1.11.3 [config/images] Pulled registry.aliyuncs.com/google_containers/pause:3.10 [config/images] Pulled registry.aliyuncs.com/google_containers/etcd:3.5.16-0 # 查看下载后的镜像 使用 ctr 命令 [root@master-01 ~]# ctr -n k8s.io image list 2.4 集群初始化 执行 kubeadm init 命令来初始化集群\n1 kubeadm init --config=kubeadm-config.yaml 如果初始化成功，我们可以看到如下信息：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Your Kubernetes control-plane has initialized successfully! To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config Alternatively, if you are the root user, you can run: export KUBECONFIG=/etc/kubernetes/admin.conf You should now deploy a pod network to the cluster. Run \u0026#34;kubectl apply -f [podnetwork].yaml\u0026#34; with one of the options listed at: https://kubernetes.io/docs/concepts/cluster-administration/addons/ Then you can join any number of worker nodes by running the following on each as root: kubeadm join 10.206.0.12:6443 --token abcdef.0123456789abcdef \\ --discovery-token-ca-cert-hash sha256:b6552d7e230e0b22b813b4a632d2154b713f97ce34894a93d59edb6e2b24bcbf 2.5 配置 kubectl 的认证文件 在 master 节点 执行以下 3 条命令，会生成文件 $HOME/.kube/config ，kubectl 需要使用此文件连接集群。\n1 2 3 mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config 2.6 验证集群 执行 kubectl get nodes 命令，可以看到 master-01 节点已经成功加入集群。\n1 2 3 [root@master-01 ~]# kubectl get nodes NAME STATUS ROLES AGE VERSION master-01 NotReady control-plane 68s v1.32.2 3. Worker 节点加入集群 （在 worker 节点执行） 在 worker 节点 只需要执行在集群初始化成功输出的 kubeadm join 命令即可。\n1 2 kubeadm join 10.206.0.12:6443 --token abcdef.0123456789abcdef \\ --discovery-token-ca-cert-hash sha256:b6552d7e230e0b22b813b4a632d2154b713f97ce34894a93d59edb6e2b24bcbf 执行成功后输出如下：\n1 2 3 4 5 6 ... ... This node has joined the cluster: * Certificate signing request was sent to apiserver and a response was received. * The Kubelet was informed of the new secure connection details. Run \u0026#39;kubectl get nodes\u0026#39; on the control-plane to see this node join the cluster. 返回 master 节点 执行 kubectl get nodes 命令，可以发现 worker 节点也成功加入了集群。\n1 2 3 4 [root@master-01 ~]# kubectl get nodes NAME STATUS ROLES AGE VERSION master-01 NotReady control-plane 4m39s v1.32.2 worker-01 NotReady \u0026lt;none\u0026gt; 2m2s v1.32.2 如果大家仔细观察会发现上面节点的状态是 NotReady ， 没有就绪，在这种状态下集群 还无法正常工作，我们需要在集群中安装一个网络插件，才可以让节点的状态变成 Ready。\n4. 安装网络插件 （在 master 节点执行） 我们使用 Calico 网络插件，calico 用到的镜像在 hub.docker.com 上，国内需要配置加速器，或者导入离线镜像。\n1 2 3 4 5 6 7 8 # 导入镜像, ctr 是 containerd 的客户端命令 ctr -n k8s.io image import calico-3.29.2.image # 下载 calico YAML 文件 curl https://raw.githubusercontent.com/projectcalico/calico/v3.29.2/manifests/calico.yaml -O # 安装 calico 插件 kubectl create -f calico.yaml 执行以下命令检查 calico 是否安装成功\n1 2 3 4 5 [root@master-01 ~]# kubectl -n kube-system get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES calico-kube-controllers-77969b7d87-ftjnt 1/1 Running 0 4m40s 10.244.184.66 master-01 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; calico-node-jlknl 1/1 Running 0 4m40s 10.206.0.10 worker-01 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; calico-node-knkhg 1/1 Running 0 4m40s 10.206.0.12 master-01 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; Calico 安装成功后，再次检查节点状态显示为 Ready。\n1 2 3 4 [root@master-01 ~]# kubectl get nodes NAME STATUS ROLES AGE VERSION master-01 Ready control-plane 57m v1.32.2 worker-01 Ready \u0026lt;none\u0026gt; 54m v1.32.2 国外最新安装\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # 1. 安装 Tigera Operator 和 CRDs kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.2/manifests/tigera-operator.yaml # 2. 安装 Operator CRDs（如果上面没包含，或单独跑） # kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.2/manifests/operator-crds.yaml # 通常 tigera-operator.yaml 已包含 # 3. 创建自定义资源（自定义 Calico 配置，这里用默认即可） # 下载模板 curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.29.2/manifests/custom-resources.yaml # 如果你的 podSubnet 不是默认的 192.168.0.0/16，需要编辑 custom-resources.yaml 中的 installation.spec.calicoNetwork.ipPools.cidr # 例如你的 kubeadm 用的是 10.244.0.0/16，就改成： # cidr: 10.244.0.0/16 #应用 kubectl create -f custom-resources.yaml kubectl apply -f custom-resources.yaml # 问题edit之后会默认运行起来 kubectl edit installation default 云虚拟主机需要开放安全组，添加对应内网ip端口\n5. 验证集群安装结果 控制面组件状态显示 Healthy\n1 2 3 4 5 6 [root@master-01 ~]# kubectl get cs Warning: v1 ComponentStatus is deprecated in v1.19+ NAME STATUS MESSAGE ERROR controller-manager Healthy ok scheduler Healthy ok etcd-0 Healthy ok 所有 Pod 状态为 Running 1 2 3 4 5 6 7 8 9 10 11 12 13 [root@master-01 ~]# kubectl get pods -A NAMESPACE NAME READY STATUS RESTARTS AGE kube-system calico-kube-controllers-77969b7d87-ftjnt 1/1 Running 0 7m27s kube-system calico-node-jlknl 1/1 Running 0 7m27s kube-system calico-node-knkhg 1/1 Running 0 7m27s kube-system coredns-6766b7b6bb-brh9d 1/1 Running 0 58m kube-system coredns-6766b7b6bb-q7pj7 1/1 Running 0 58m kube-system etcd-master-01 1/1 Running 1 58m kube-system kube-apiserver-master-01 1/1 Running 1 58m kube-system kube-controller-manager-master-01 1/1 Running 1 58m kube-system kube-proxy-498gc 1/1 Running 0 58m kube-system kube-proxy-bbqcf 1/1 Running 0 55m kube-system kube-scheduler-master-01 1/1 Running 1 58m 6. 常见故障处理 6.1 镜像拉取失败 如果 Pod 状态显示 ImagePullBackOff，表示容器所有的节点 镜像拉取失败，需要到对应节点上手动拉取镜像或者导入离线镜像即可。\n6.2 Master 节点初始化失败 在 master 节点初始化过程中，常见的故障是 卡在如下界面，此处是正在等待 kube-apiserver 启动成功，如果apiserver启动失败，这里会一直等待，直到4分钟超时会有报错。 排错的时候需要查看 messages 日志定位具体原因，在初始化的过程中建议打开一个shell 终端运行 tail -f /var/log/messages 命令，注意观察报错信息。\n6.3 Worker 节点加入集群失败 Worker 节点在加入集群过程中需要成功运行 kubelet 服务，再连接 kube-apiserver，任何一步失败都无法加入集群，因此在执行 kubeadm join 时也需要打开一个shell 终端运行 tail -f /var/log/messages 命令，观察日志中的报错信息。\n6.4 重置集群 如果找到了报错原因，需要重新初始化集群或是worker节点重新加入集群， 都需要先执行 kubeadm reset 命令，再执行 kubeadm init 或是 kubeadm join。\n6.5 重新生成 join 命令 初始化时生成的 kubeadm join 命令，有效期是 24 小时，超时后命令中的token 就失效了，如果 24小时后有节点加入集群， 我们需要执行以下命令，重新生成可用的 token 。\n1 2 [root@master-01 ~]# kubeadm token create --print-join-command kubeadm join 10.206.0.12:6443 --token sabx6g.kyas4pu9guhio7iv --discovery-token-ca-cert-hash sha256:b6552d7e230e0b22b813b4a632d2154b713f97ce34894a93d59edb6e2b24bcbf 参考文档 Containerd 官网： https://containerd.io/\nContainerd 官方文档： https://github.com/containerd/containerd/blob/main/docs/getting-started.md\nKubeadm 官方文档： https://kubernetes.io/zh-cn/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/\nCalico 官方文档： https://docs.tigera.io/calico/latest/getting-started/kubernetes/self-managed-onprem/onpremises#install-calico\nKubernetes 每年更新证书和99年证书: https://mp.weixin.qq.com/s/oWVZsTdeS-4coEBqwRLF_g\nContainerd 容器运行时 使用 容器运行时 Containerd 安装与使用\nCRI 客户端 crictl 命令介绍\n容器命令行工具 nerdctl\n","date":"2026-04-06T16:05:38+08:00","image":"https://cdn.jsdelivr.net/gh/luckycloveryh/picgo-bed@main/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260524195140_504_12.png","permalink":"https://luckycloveryh.github.io/fuyou/p/kubernetes-%E5%AE%89%E8%A3%85%E9%83%A8%E7%BD%B2/","title":"Kubernetes 安装部署"},{"content":"Restful API Restful 是目前最流行的 API 设计规范，用于 Web 数据接口的设计。 RESTful 的核心思想就是，客户端发出的数据操作指令都是\u0026quot;动词 + 宾语\u0026quot;的结构。比如，GET /articles这个命令，GET是动词，/articles是宾语。 动词通常就是五种 HTTP 方法，对应 CRUD 操作。\nGET：读取（Read） POST：新建（Create） PUT：更新（Update） PATCH：更新（Update），通常是部分更新 DELETE：删除（Delete） 根据 HTTP 规范，动词一律大写。 curl -XGET 127.0.0.1:8080/books\ncurl -XPOST -H \u0026lsquo;Content-Type: application/json\u0026rsquo; -d \u0026lsquo;{ \u0026ldquo;title\u0026rdquo;: \u0026ldquo;Three-Body\u0026rdquo;, \u0026ldquo;author\u0026rdquo;: \u0026ldquo;Liucixin\u0026rdquo; }\u0026rsquo; http://127.0.0.1:8080/books\ncurl -XDELETE http://127.0.0.1:8080/books/1\ncurl -XPATCH -H \u0026lsquo;Content-Type: application/json\u0026rsquo; -d \u0026lsquo;{ \u0026ldquo;title\u0026rdquo;: \u0026ldquo;The Three-Body\u0026rdquo; }\u0026rsquo; http://127.0.0.1:8080/books/2\ncurl -XGET 127.0.0.1:80/api/v1/books/3\n状态码\n1xx：相关信息 2xx：操作成功 3xx：重定向 4xx：客户端错误 5xx：服务器错误 Raft https://thesecretlivesofdata.com/raft/\n","date":"2026-04-06T16:05:37+08:00","image":"https://cdn.jsdelivr.net/gh/luckycloveryh/picgo-bed@main/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260524195229_528_12.jpg","permalink":"https://luckycloveryh.github.io/fuyou/p/kubernetes-%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86restful-api-%E4%B8%8E-raft-%E7%AE%97%E6%B3%95%E8%AF%A6%E8%A7%A3/","title":"Kubernetes 核心原理：RESTful API 与 Raft 算法详解"},{"content":" 什么是容器编排 容器技术的核心概念是容器、镜像、仓库，使用这三大基本要素我们就可以轻松地完成应用的打包、分发工作，实现“一次构建，到处运行”的梦想。\n不过，当我们熟练地掌握了容器技术，信心满满地要在服务器集群里大规模实施的时候，却会发现容器技术的创新只是解决了运维部署工作中一个很小的问题。现实生产环境的复杂程度实在是太高了，除了最基本的安装，还会有各式各样的需求，比如服务发现、负载均衡、状态监控、健康检查、扩容缩容、应用迁移、高可用等等。\n虽然容器技术开启了云原生时代，但它也只走出了一小步，再继续前进就无能为力了，因为这已经不再是隔离一两个进程的普通问题，而是要隔离数不清的进程，还有它们之间互相通信、互相协作的超级问题，困难程度是指数级别的上升。\n这些容器之上的管理、调度工作，就是这些年最流行的词汇：“容器编排”（Container Orchestration）。\n容器编排这个词听起来好像挺高大上，但如果你理解了之后就会发现其实也并不神秘。像我们在 Docker 课里使用 Docker Compose 部署 WordPress 网站的时候，就是一种在单机环境下的容器编排。\n面对单机上的几个容器，Docker Compose 还可以应付，但如果规模上到几百台服务器、成千上万的容器，处理它们之间的复杂联系就必须要依靠计算机了，而目前计算机用来调度管理的“事实标准”就是 Kubernetes。\n什么是 Kubernetes Kubernetes 背后有 Borg 系统十多年生产环境经验的支持，技术底蕴深厚，理论水平也非常高，一经推出就引起了轰动。然后在 2015 年，Google 又联合 Linux 基金会成立了CNCF（Cloud Native Computing Foundation，云原生基金会），并把 Kubernetes 捐献出来作为种子项目。\n有了 Google 和 Linux 这两大家族的保驾护航，再加上宽容开放的社区，作为 CNCF 的“头把交椅”，Kubernetes 旗下很快就汇集了众多行业精英，仅用了两年的时间就打败了同期的竞争对手 Apache Mesos 和 Docker Swarm，成为了这个领域的唯一霸主。\n那么，Kubernetes 到底能够为我们做什么呢？ 简单来说，Kubernetes 就是一个生产级别的容器编排平台和集群管理系统，不仅能够创建、调度容器，还能够监控、管理服务器，它凝聚了 Google 等大公司和开源社区的集体智慧，从而让中小型公司也可以具备轻松运维海量计算节点——也就是“云计算”的能力。\nKubernetes 的官网（https://kubernetes.io/zh/），里面有非常详细的文档，包括概念解释、入门教程、参考手册等等，最难得的是它有全中文版本，我们阅读起来完全不会有语言障碍，如果你有时间可以多上去看看，及时获取官方第一手知识。\n云原生时代的操作系统 Kubernetes 是一个生产级别的容器编排平台和集群管理系统，能够创建、调度容器，监控、管理服务器。 容器是什么？容器是软件，是应用，是进程。服务器是什么？服务器是硬件，是 CPU、内存、硬盘、网卡。那么，既可以管理软件，也可以管理硬件，这样的东西应该是什么？这就是一个操作系统（Operating System）！\n没错，从某种角度来看，Kubernetes 可以说是一个集群级别的操作系统，主要功能就是资源管理和作业调度。但 Kubernetes 不是运行在单机上管理单台计算资源和进程，而是运行在多台服务器上管理几百几千台的计算资源，以及在这些资源上运行的上万上百万的进程，规模要大得多。\n所以，你可以把 Kubernetes 与 Linux 对比起来学习，而这个新的操作系统里自然会有一系列新名词、新术语，你也需要使用新的思维方式来考虑问题。\nKubernetes 的基本架构 Kubernetes 采用的是 “控制面 / 数据面”（Control Plane / Data Plane）架构，集群里的计算机被称为“节点”（Node），可以是实机也可以是虚机，少量的节点用作控制面来执行集群的管理维护工作，其他的大部分节点都被划归数据面，用来跑业务应用。\n控制面的节点在 Kubernetes 里叫做 Master Node，一般简称为 Master，它是整个集群里最重要的部分，可以说是 Kubernetes 的大脑和心脏。\n数据面的节点叫做 Worker Node，一般就简称为 Worker 或者 Node，相当于 Kubernetes 的手和脚，在 Master 的指挥下干活 。\nNode 的数量非常多，构成了一个资源池，Kubernetes 就在这个池里分配资源，调度应用。因为资源被“池化”了，所以管理也就变得比较简单，可以在集群中任意添加或者删除节点。\n在这张架构图里，我们还可以看到有一个 kubectl，它就是 Kubernetes 的客户端工具，用来操作 Kubernetes，但它位于集群之外，理论上不属于集群。\n可以使用命令 kubectl get node 来查看 Kubernetes 的节点状态：\n1 2 3 4 [root@master1 ~]# kubectl get nodes NAME STATUS ROLES AGE VERSION master1 Ready control-plane,master 34m v1.23.15 node1 Ready worker 34m v1.23.15 可以看到当前集群中有一个控制面的 Master 节点和一个数据面的 Worker 节点，如果集群规模较小，也可以把 Master 和 Worker 部署在一个节点上。\n节点内部的结构 Kubernetes 的节点内部也具有复杂的结构，是由很多的模块构成的，这些模块又可以分成组件（Component）和插件（Addon）两类。\n组件实现了 Kubernetes 的核心功能特性，没有这些组件 Kubernetes 就无法启动，而插件则是 Kubernetes 的一些附加功能，属于“锦上添花”，不安装也不会影响 Kubernetes 的正常运行。\nMaster 里的组件有哪些 Master 里有 4 个组件，分别是 apiserver、etcd、scheduler、controller-manager。\nkube-apiserver 是 Master 节点——同时也是整个 Kubernetes 系统的唯一入口，它对外公开了一系列的 RESTful API，并且加上了验证、授权等功能，所有其他组件都只能和它直接通信，可以说是 Kubernetes 里的联络员。\netcd`` 是一个高可用的分布式 Key-Value 数据库，用来持久化存储系统里的各种资源对象和状态，相当于 Kubernetes 里的配置管理员。注意它只与 apiserver 有直接联系，也就是说任何其他组件想要读写 etcd 里的数据都必须经过 apiserver。\nkube-scheduler 负责容器的编排工作，检查节点的资源状态，把 Pod 调度到最适合的节点上运行，相当于部署人员。因为节点状态和 Pod 信息都存储在 etcd 里，所以 scheduler 必须通过apiserver 才能获得。\nkube-controller-manager 负责维护容器和节点等资源的状态，实现故障检测、服务迁移、应用伸缩等功能，相当于监控运维人员。同样地，它也必须通过 apiserver 获得存储在 etcd 里的信息，才能够实现对资源的各种操作。\n这 4 个组件也都被容器化了，运行在集群的 Pod 里，我们可以用 kubectl 来查看它们的状态，使用命令：\n1 2 3 4 5 [root@master1 ~]# kubectl -n kube-system get pods NAME READY STATUS RESTARTS AGE kube-apiserver-master1 1/1 Running 0 53m kube-controller-manager-master1 1/1 Running 0 53m kube-scheduler-master1 1/1 Running 0 53m 注意命令行里要用 -n kube-system 参数，表示检查“kube-system”名字空间里的 Pod，至于名字空间是什么，我们后面会讲到。\nNode 里的组件有哪些 Master 里的 kube-apiserver、kube-scheduler 等组件需要获取节点的各种信息才能够作出管理决策，那这些信息该怎么来呢？ 这就需要 Node 里的 3 个组件了，分别是 kubelet、kube-proxy、container-runtime。\nkubelet 是 Node 的代理，负责管理 Node 相关的绝大部分操作，Node 上只有它能够与 kube-apiserver 通信，实现状态报告、命令下发、启停容器等功能，相当于是 Node 上的一个“小管家”。\nkube-proxy 的作用有点特别，它是 Node 的网络代理，只负责管理容器的网络通信，简单来说就是为 Pod 转发 TCP/UDP 数据包，相当于是专职的“小邮差”。\n第三个组件 container-runtime 我们就比较熟悉了，它是容器和镜像的实际使用者，在 kubelet 的指挥下创建容器，管理 Pod 的生命周期，是真正干活的“苦力”。\n我们一定要注意，因为 Kubernetes 的定位是容器编排平台，所以它没有限定 container runtime 必须是 Docker，完全可以替换成任何符合标准的其他容器运行时，例如 containerd、CRI-O 等等，只不过在这里我们使用的是 Docker。\n这 3 个组件中只有 kube-proxy 被容器化了，而 kubelet 因为必须要管理整个节点，容器化会限制它的能力，所以它必须在 container-runtime 之外运行。\n现在，我们再把 Node 里的组件和 Master 里的组件放在一起来看，就能够明白 Kubernetes 的大致工作流程了：\n每个 Node 上的 kubelet 会定期向 kube-apiserver 上报节点状态，kube-apiserver 再存到 etcd 里。 每个 Node 上的 kube-proxy 实现了 TCP/UDP 反向代理，让容器对外提供稳定的服务。 kube-scheduler 通过 kube-apiserver 得到当前的节点状态，调度 Pod，然后 kube-apiserver 下发命令给某 个 Node 上的 kubelet，kubelet 调用 container-runtime 启动容器。 controller-manager 也通过 kube-apiserver 得到实时的节点状态，监控可能的异常情况，再使用相应的手段去调节恢复。 其实，这和我们在 Kubernetes 出现之前的操作流程也差不了多少，但 Kubernetes 的高明之处就在于把这些都抽象化规范化了。\n于是，这些组件就好像是无数个不知疲倦的运维工程师，把原先繁琐低效的人力工作搬进了高效的计算机里，就能够随时发现集群里的变化和异常，再互相协作，维护集群的健康状态。\n插件（Addons）有哪些 只要服务器节点上运行了 kube-apiserver、kube-scheduler、kube-controller-manager、etcd、kubelet、kube-proxy、container-runtime 组件，就可以说是一个功能齐全的 Kubernetes 集群了。\n由于 Kubernetes 本身的设计非常灵活，所以就有大量的插件用来扩展、增强它对应用和集群的管理能力。\n常用的插件有：\nDNS: 负责为整个集群提供DNS服务； Ingress Controller：为服务提供外网入口； MetricsServer：提供资源监控； Dashboard：提供GUI； ","date":"2026-04-06T16:05:34+08:00","image":"https://cdn.jsdelivr.net/gh/luckycloveryh/picgo-bed@main/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260524195125_497_12.png","permalink":"https://luckycloveryh.github.io/fuyou/p/kubernetes-%E6%A0%B8%E5%BF%83%E7%BB%84%E4%BB%B6%E5%85%A8%E8%A7%A3%E6%9E%90%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E7%B2%BE%E9%80%9A/","title":"Kubernetes 核心组件全解析：从入门到精通"},{"content":"什么是 Hugo？ Hugo 是一个用 Go 语言编写的静态网站生成器，具有以下特点：\n⚡ 超快的性能 - 可以在几毫秒内生成数千个网页 📝 易于使用 - 简单的命令行界面 🎨 灵活的主题系统 - 丰富的社区主题可选 📱 完全响应式 - 适配所有设备 Stack 主题介绍 Stack 是一个现代化的 Hugo 主题，提供了：\n优雅的设计 快速的加载速度 完整的功能支持 良好的 SEO 优化 主题特性 1 2 3 4 5 6 features: - 响应式设计 - 代码高亮 - 评论支持 - 搜索功能 - 暗黑模式 快速开始 安装 Hugo 1 2 3 4 5 6 7 8 # Windows 使用 choco choco install hugo-extended # macOS 使用 homebrew brew install hugo # Linux 使用 apt sudo apt-get install hugo 创建新博客 1 2 3 hugo new site my-blog cd my-blog git clone https://github.com/CaiJimmy/hugo-theme-stack themes/hugo-theme-stack 内容优化建议 SEO 最佳实践 使用有意义的标题 添加元描述 合理使用标签和分类 包含高质量的图片 性能优化 压缩图片 使用 CDN 启用缓存 最小化 CSS/JS 总结 使用 Hugo + Stack 主题可以快速搭建一个高性能、美观的静态博客。如果你想了解更多细节，可以查阅官方文档。\n下一篇文章预告： 如何在 Hugo 中使用自定义主题和插件\n","date":"2026-04-06T10:00:00+08:00","image":"https://cdn.jsdelivr.net/gh/luckycloveryh/picgo-bed@main/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260524195143_505_12.png","permalink":"https://luckycloveryh.github.io/fuyou/p/hugo--stack-%E4%B8%BB%E9%A2%98%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97/","title":"Hugo + Stack 主题完全指南"},{"content":"标题 Markdown 使用 # 表示标题，越多 # 表示越低级的标题。\n1 2 3 # 一级标题 ## 二级标题 ### 三级标题 文本格式 强调 加粗文本 使用 ** 或 __ 斜体文本 使用 * 或 _ 粗斜体 使用 *** 删除线 使用 ~~ 引用 这是一个引用\n引用可以包含多行\n列表 无序列表 项目1 项目2 嵌套项目 另一个嵌套项目 项目3 有序列表 第一项 第二项 第三项 嵌套项 嵌套项 代码 行内代码 使用 code 来表示行内代码。\n代码块 1 2 3 def hello_world(): print(\u0026#34;Hello, World!\u0026#34;) return True 1 2 const greeting = \u0026#34;Hello, Markdown!\u0026#34;; console.log(greeting); 链接和图片 链接 Google\nReference Link\n图片 表格 特性 Stack 其他主题 性能 ⭐⭐⭐⭐⭐ ⭐⭐⭐ 美观 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ 易用 ⭐⭐⭐⭐ ⭐⭐⭐ 分隔线 总结 Markdown 是一种简单而强大的文本格式化方式，非常适合博客写作。熟练使用这些语法可以大大提高你的写作效率。\n","date":"2026-04-05T14:30:00+08:00","image":"https://cdn.jsdelivr.net/gh/luckycloveryh/picgo-bed@main/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260524195148_507_12.png","permalink":"https://luckycloveryh.github.io/fuyou/p/markdown-%E5%AE%8C%E5%85%A8%E8%AF%AD%E6%B3%95%E6%95%99%E7%A8%8B/","title":"Markdown 完全语法教程"},{"content":"什么是前端性能？ 前端性能是指网页在浏览器中加载、渲染和交互的速度。它影响用户体验，同时也是搜索引擎排名的重要因素。\n性能指标 First Contentful Paint (FCP) - 首次内容绘制 Largest Contentful Paint (LCP) - 最大内容绘制 Cumulative Layout Shift (CLS) - 累积布局偏移 First Input Delay (FID) - 首次输入延迟 性能优化策略 1. 图片优化 使用现代格式（WebP） 响应式图片（srcset） 懒加载 压缩和裁剪 1 2 3 4 5 6 \u0026lt;img src=\u0026#34;image.jpg\u0026#34; srcset=\u0026#34;image-small.jpg 480w, image-large.jpg 1200w\u0026#34; alt=\u0026#34;描述\u0026#34; loading=\u0026#34;lazy\u0026#34; /\u0026gt; 2. JavaScript 优化 代码分割\n1 2 // 动态导入 const module = await import(\u0026#39;./module.js\u0026#39;); 体积优化\n移除未使用的代码 使用 tree-shaking 压缩和混淆 使用 CDN 3. CSS 优化 压缩 CSS 文件 移除重复的选择器 使用 CSS 变量避免重复 内联关键 CSS 1 2 3 4 5 6 7 /* 关键 CSS - 直接内联 */ @media (prefers-color-scheme: dark) { body { background: #1a1a1a; color: #ffffff; } } 4. 网络优化 技术 效果 实施难度 缓存策略 高 低 HTTP/2 中 中 CDN 高 低 预连接 中 低 预加载 高 中 1 2 3 4 5 6 7 8 \u0026lt;!-- 预连接 --\u0026gt; \u0026lt;link rel=\u0026#34;preconnect\u0026#34; href=\u0026#34;https://cdn.example.com\u0026#34;\u0026gt; \u0026lt;!-- 预加载关键资源 --\u0026gt; \u0026lt;link rel=\u0026#34;preload\u0026#34; href=\u0026#34;critical.js\u0026#34; as=\u0026#34;script\u0026#34;\u0026gt; \u0026lt;!-- DNS 预解析 --\u0026gt; \u0026lt;link rel=\u0026#34;dns-prefetch\u0026#34; href=\u0026#34;https://api.example.com\u0026#34;\u0026gt; 5. 渲染性能 避免强制同步布局 1 2 3 4 5 6 7 8 9 10 // ❌ 不好 - 产生布局抖动 for (let i = 0; i \u0026lt; 100; i++) { element.style.width = element.offsetWidth + 1 + \u0026#39;px\u0026#39;; } // ✅ 好 - 批量操作 const width = element.offsetWidth; for (let i = 0; i \u0026lt; 100; i++) { element.style.width = width + i + \u0026#39;px\u0026#39;; } 使用 requestAnimationFrame 1 2 3 4 5 6 function animate() { // 执行动画帧 element.style.transform = `translateX(${x}px)`; requestAnimationFrame(animate); } animate(); 监测和分析 使用 Lighthouse 1 2 3 # 使用 Chrome DevTools 或命令行 npm install -g lighthouse lighthouse https://example.com 实时监测 Web Vitals Performance API Google Analytics 自定义指标 1 2 3 4 5 6 7 8 // 测量 FCP performance.measureUserAgentSpecificMemory?.() new PerformanceObserver((entryList) =\u0026gt; { const entries = entryList.getEntries(); entries.forEach(entry =\u0026gt; { console.log(entry.name, entry.duration); }); }).observe({ entryTypes: [\u0026#39;paint\u0026#39;, \u0026#39;measure\u0026#39;] }); 总结 前端性能优化是一个持续的过程：\n✅ 定期测量性能指标 ✅ 确定瓶颈 ✅ 实施优化方案 ✅ 验证改进效果 ✅ 重复此过程 记住：性能优化永无止境，但永远是值得的！\n推荐阅读：\nGoogle 性能指南 MDN - Web 性能 ","date":"2026-04-04T09:15:00+08:00","image":"https://cdn.jsdelivr.net/gh/luckycloveryh/picgo-bed@main/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260524195150_508_12.png","permalink":"https://luckycloveryh.github.io/fuyou/p/%E5%89%8D%E7%AB%AF%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97/","title":"前端性能优化完全指南"}]