k8s yaml 如何编写

yaml 语法

数组

1# YAML数组(列表)
2OS:
3  - linux
4  - macOS
5  - Windows

等价

1{
2  "OS": ["linux", "macOS", "Windows"]
3}

字典

1
2# YAML对象(字典)
3Kubernetes:
4  master: 1
5  worker: 3

等价

1{
2  "Kubernetes": {
3    "master": 1,
4    "worker": 3
5  }
6}
1# 复杂的例子,组合数组和对象
2Kubernetes:
3  master:
4    - apiserver: running
5    - etcd: running
6  node:
7    - kubelet: running
8    - kube-proxy: down
9    - container-runtime: [docker, containerd, cri-o]

API 对象

1kubectl api-resources # 来查看当前 Kubernetes 版本支持的所有对象

API 对象采用标准的 HTTP 协议,为了方便理解,我们可以借鉴一下 HTTP 的报文格式,把 API 对象的描述分成“header”和“body”两部分。 “header”包含的是 API 对象的基本信息,有三个字段:apiVersion、kind、metadata。

apiVersion、kind、metadata 都被 kubectl 用于生成 HTTP 请求发给 apiserver,你可以用 –v=9 参数在请求的 URL 里看到它们

  • apiVersion 表示操作这种资源的 API 版本号,由于 Kubernetes 的迭代速度很快,不同的版本创建的对象会有差异,为了区分这些版本就需要使用 apiVersion 这个字段,比如 v1、v1alpha1、v1beta1 等等。
  • kind 表示资源对象的类型,这个应该很好理解,比如 Pod、Node、Job、Service 等等。
  • metadata 这个字段顾名思义,表示的是资源的一些“元信息”,也就是用来标记对象,方便 Kubernetes 管理的一些信息。
1apiVersion: v1
2kind: Pod
3metadata:
4  name: ngx-pod
5  labels:
6    env: demo
7    owner: chrono

生成的http url

1https://192.168.49.2:8443/api/v1/namespaces/default/pods/ngx-pod

如何编写k8s yaml

  1. 官方文档

  2. kubectl 的两个特殊参数 –dry-run=client -o yaml,前者是空运行,后者是生成 YAML 格式,结合起来使用就会让 kubectl 不会有实际的创建动作,而只生成 YAML 文件。

1vagrant@master:~$ kubectl run ngx1 --image=nginx:alpine --dry-run=client -o yaml
 1apiVersion: v1     #这种资源的 API 版本号
 2kind: Pod          #kind 表示资源对象的类型
 3metadata:          #metadata 这个字段顾名思义,表示的是资源的一些“元信息”
 4  creationTimestamp: null
 5  labels:
 6    run: ngx1
 7  name: ngx1
 8spec:
 9  containers:
10  - image: nginx:alpine
11    name: ngx1
12    resources: {}
13  dnsPolicy: ClusterFirst
14  restartPolicy: Always
15status: {}
  1. 命令 kubectl explain,它相当于是 Kubernetes 自带的 API 文档
1kubectl explain pod
2kubectl explain pod.metadata
3kubectl explain pod.spec
4kubectl explain pod.spec.containers

pod

pod介绍

为了解决这样多应用联合运行的问题,同时还要不破坏容器的隔离,就需要在容器外面再建立一个“收纳舱”,让多个容器既保持相对独立,又能够小范围共享网络、存储等资源,而且永远是“绑在一起”的状态

Pod 作为应用调度部署的最小单位

ports:列出容器对外暴露的端口,和 Docker 的 -p 参数有点像。

imagePullPolicy:指定镜像的拉取策略,可以是 Always/Never/IfNotPresent,一般默认是 IfNotPresent,也就是说只有本地不存在才会远程拉取镜像,可以减少网络消耗。

env:定义 Pod 的环境变量,和 Dockerfile 里的 ENV 指令有点类似,但它是运行时指定的,更加灵活可配置。

command:定义容器启动时要执行的命令,相当于 Dockerfile 里的 ENTRYPOINT 指令。

args:它是 command 运行时的参数,相当于 Dockerfile 里的 CMD 指令,这两个命令和 Docker 的含义不同,要特别注意。

 1apiVersion: v1
 2kind: Pod       #类型
 3metadata:
 4  name: busy-pod #pod 名
 5  labels:    #pod 标签
 6    owner: chrono
 7    env: demo
 8    region: north
 9    tier: back
10spec:
11  containers:    #运行的容器 数组 
12  - image: busybox:latest    #镜像 
13    name: busy           # 容器名
14    imagePullPolicy: IfNotPresent  #镜像拉取方式
15    env:                      # 容器环境变量
16      - name: os
17        value: "ubuntu"
18      - name: debug
19        value: "on"
20    command:         # 类型 docker ENTRYPOINT 指令
21      - /bin/echo
22    args:
23      - "$(os), $(debug)"             # 命令参数

操作pod

 1kubectl apply -f busy-pod.yml
 2kubectl delete -f busy-pod.yaml
 3kubectl delete pod busy-pod
 4kubectl logs  busy-pod
 5kubectl get pod
 6kubectl describe pod busy-pod
 7
 8# kubectl 也提供与 docker 类似的 cp 和 exec 命令
 9kubectl cp a.txt ngx-pod:/tmp
10
11#进入pod  kubectl exec 的命令格式与 Docker 有一点小差异,需要在 Pod 后面加上 --  ,kubectl 的命令与 Shell 命令分隔开
12kubectl exec -it ngx-pod -- sh

Job/CronJob

为什么不直接用Pod来处理业务?

Kubernetes 对象设计思路,一个是“单一职责”,另一个是“组合优于继承”。

因为 Pod 已经是一个相对完善的对象,专门负责管理容器,那么我们就不应该再“画蛇添足”地盲目为它扩充功能,而是要保持它的独立性,容器之外的功能就需要定义其他的对象,把 Pod 作为它的一个成员“组合”进去。

Kubernetes 里,“临时任务”就是 API 对象 Job,“定时任务”就是 API 对象 CronJob

如何使用 YAML 描述 Job

1export out="--dry-run=client -o yaml"              # 定义Shell变量
2kubectl create job echo-job --image=busybox $out
 1apiVersion: batch/v1
 2kind: Job
 3metadata:
 4  name: echo-job
 5spec:
 6  template:
 7    spec:
 8      restartPolicy: OnFailure
 9      containers:
10      - image: busybox
11        name: echo-job
12        imagePullPolicy: IfNotPresent
13        command: ["/bin/echo"]
14        args: ["hello", "world"]

template 字段定义了一个“应用模板”,里面嵌入了一个 Pod,这样 Job 就可以从这个模板来创建出 Pod

因为 Job 业务的特殊性,所以我们还要在 spec 里多加一个字段 restartPolicy,确定 Pod 运行失败时的策略,OnFailure 是失败原地重启容器,而 Never 则是不重启容器,让 Job 去重新调度生成一个新的 Pod

如何在 Kubernetes 里操作 Job

1kubectl apply -f job.yml
2kubectl get job
3kubectl  get pod
4kubectl  logs `job_pod_name`

列出几个控制离线作业的重要字段,其他更详细的信息可以参考 Job 文档:

activeDeadlineSeconds,设置 Pod 运行的超时时间。

backoffLimit,设置 Pod 的失败重试次数。

completions,Job 完成需要运行多少个 Pod,默认是 1 个。

parallelism,它与 completions 相关,表示允许并发运行的 Pod 数量,避免过多占用资源。

要注意这 4 个字段并不在 template 字段下,而是在 spec 字段下,所以它们是属于 Job 级别的,用来控制模板里的 Pod 对象

 1apiVersion: batch/v1
 2kind: Job
 3metadata:
 4  name: sleep-job
 5spec:
 6  activeDeadlineSeconds: 15
 7  backoffLimit: 2
 8  completions: 4
 9  parallelism: 2
10  template:
11    spec:
12      restartPolicy: OnFailure
13      containers:
14      - image: busybox
15        name: echo-job
16        imagePullPolicy: IfNotPresent
17        command:
18          - sh
19          - -c
20          - sleep $(($RANDOM % 10 + 1)) && echo done

如何使用 YAML 描述 CronJob

因为 CronJob 的名字有点长,所以 Kubernetes 提供了简写 cj,这个简写也可以使用命令 kubectl api-resources 看到;第二,CronJob 需要定时运行,所以我们在命令行里还需要指定参数 –schedule

1export out="--dry-run=client -o yaml"              # 定义Shell变量
2kubectl create cj echo-cj --image=busybox --schedule="" $out

然后我们编辑这个 YAML 样板,生成 CronJob 对象:

cronjob.yml

 1apiVersion: batch/v1
 2kind: CronJob
 3metadata:
 4  name: echo-cj
 5spec:
 6  schedule: '*/1 * * * *'         # 标准的 Cron 语法
 7  successfulJobsHistoryLimit: 5    # 保留历史多少个,每执行一次会产生一个pod
 8  jobTemplate:                    # cronjob 下的 job 模板
 9    spec:
10      template:                   # job 下的 pod 模板
11        spec:
12          restartPolicy: OnFailure
13          containers:
14          - image: busybox
15            name: echo-cj
16            imagePullPolicy: IfNotPresent
17            command: ["/bin/echo"]
18            args: ["hello", "world"]

我们还是重点关注它的 spec 字段,你会发现它居然连续有三个 spec 嵌套层次:

第一个 spec 是 CronJob 自己的对象规格声明

第二个 spec 从属于“jobTemplate”,它定义了一个 Job 对象。

第三个 spec 从属于“template”,它定义了 Job 里运行的 Pod。

CronJob 其实是又组合了 Job 而生成的新对象

1kubectl apply -f cronjob.yml
2kubectl get cj
3kubectl get pod

ConfigMap/Secret

容器管理配置方式

两种管理配置文件的方式。第一种是编写 Dockerfile,用 COPY 指令把配置文件打包到镜像里;第二种是在运行时使用 docker cp 或者 docker run -v,把本机的文件拷贝进容器

这两种方式都存在缺陷。第一种方法相当于是在镜像里固定了配置文件,不好修改,不灵活,第二种方法则显得有点“笨拙”,不适合在集群中自动化运维管理

数据安全的角度来看可以分成两类

  • 明文配置 比如服务端口、运行参数、文件路径等等。

  • 机密配置,由于涉及敏感信息需要保密,不能随便查看,比如密码、密钥、证书等等

ConfigMap

先来看 ConfigMap,它有简写名字“cm”,所以命令行里没必要写出它的全称

1export out="--dry-run=client -o yaml"        # 定义Shell变量
2kubectl create cm info $out
1apiVersion: v1
2kind: ConfigMap
3metadata:
4  creationTimestamp: null
5  name: info

kubectl create 后面多加一个参数 –from-literal ,表示从字面值生成一些数据:

1kubectl create cm info --from-literal=k=v --dry-run=client -o yaml

cm.yaml

 1apiVersion: v1
 2kind: ConfigMap
 3metadata:
 4  creationTimestamp: null
 5  name: info
 6data:
 7  count: '10'
 8  debug: 'on'
 9  path: '/etc/systemd'
10  greeting: |
11        say hello to kubernetes.
1kubectl apply -f cm.yaml
2kubectl get cm
3
4vagrant@master:~/k8syaml$ kubectl get cm
5NAME               DATA   AGE
6info               4      157m
7kube-root-ca.crt   1      7d13h
 1vagrant@master:~/k8syaml$ kubectl describe cm info
 2Name:         info
 3Namespace:    default
 4Labels:       <none>
 5Annotations:  <none>
 6
 7Data
 8====
 9count:
10----
1110
12debug:
13----
14on
15greeting:
16----
17say hello to kubernetes.
18
19path:
20----
21/etc/systemd
22
23BinaryData
24====
25
26Events:  <none>

ConfigMap 的 Key-Value 信息就已经存入了 etcd 数据库,后续就可以被其他 API 对象使用

Secret

Kubernetes 里 Secret 对象又细分出很多类如

  • 访问私有镜像仓库的认证信息

  • 身份识别的凭证信息

  • HTTPS 通信的证书和私钥

  • 一般的机密信息(格式由用户自行解释) generic

  • 等等….

1kubectl create secret generic user --from-literal=name=root  --dry-run=client -o yaml

得到

1apiVersion: v1
2kind: Secret
3metadata:
4  name: user
5data:
6  name: cm9vdA==       #默认base64加密 
1vagrant@master:~/k8syaml$ echo -n "root" | base64
2cm9vdA==

手动加几个配置

secret.yml

1apiVersion: v1
2kind: Secret
3metadata:
4  name: user
5data:
6  name: cm9vdA==  # root
7  pwd: MTIzNDU2   # 123456
8  db: bXlzcWw=    # mysql
 1vagrant@master:~/k8syaml$ kubectl describe secret user
 2Name:         user
 3Namespace:    default
 4Labels:       <none>
 5Annotations:  <none>
 6
 7Type:  Opaque
 8
 9Data
10====
11db:    5 bytes
12name:  4 bytes
13pwd:   6 bytes

使用 kubectl describe 不能直接看到内容,只能看到数据的大小

如何以环境变量的方式使用 ConfigMap/Secret

容器的字段“containers”里有一个“env”,它定义了 Pod 里容器能够看到的环境变量

当时我们只使用了简单的“value”,把环境变量的值写“死”在了 YAML 里,实际上它还可以使用另一个“valueFrom”字段,从 ConfigMap 或者 Secret 对象里获取值,这样就实现了把配置信息以环境变量的形式注入进 Pod,也就是配置与应用的解耦。

1kubectl explain pod.spec.containers.env.valueFrom

valueFrom”字段指定了环境变量值的来源,可以是“configMapKeyRef”或者“secretKeyRef”,然后你要再进一步指定应用的 ConfigMap/Secret 的“name”和它里面的“key”,要当心的是这个“name”字段是 API 对象的名字,而不是 Key-Value 的名字

定义一个pod env 取 configmap 和 secret的 demo

pod-env-from-configmap-and-secret.yaml

 1apiVersion: v1
 2kind: Pod
 3metadata:
 4  name: env-pod
 5spec:
 6  containers:
 7  - env:
 8      - name: COUNT
 9        valueFrom:
10          configMapKeyRef:
11            name: info
12            key: count
13      - name: GREETING
14        valueFrom:
15          configMapKeyRef:
16            name: info
17            key: greeting
18      - name: USERNAME
19        valueFrom:
20          secretKeyRef:
21            name: user
22            key: name
23      - name: PASSWORD
24        valueFrom:
25          secretKeyRef:
26            name: user
27            key: pwd
28    image: busybox
29    name: busy
30    imagePullPolicy: IfNotPresent
31    command: ["/bin/sleep", "300"]

进入pod看环境变量 是否已经设置

 1kubectl apply -f  pod-env-from-configmap-and-secret.yaml 
 2vagrant@master:~/k8syaml$ kubectl exec -it env-pod -- sh
 3/ # ls
 4bin   dev   etc   home  proc  root  sys   tmp   usr   var
 5/ # printenv
 6KUBERNETES_SERVICE_PORT=443
 7KUBERNETES_PORT=tcp://10.96.0.1:443
 8HOSTNAME=env-pod
 9SHLVL=1
10HOME=/root
11GREETING=say hello to kubernetes.    # 保留了 换行符
12
13TERM=xterm
14USERNAME=root
15KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
16PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
17COUNT=10
18KUBERNETES_PORT_443_TCP_PORT=443
19KUBERNETES_PORT_443_TCP_PROTO=tcp
20KUBERNETES_SERVICE_PORT_HTTPS=443
21KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
22KUBERNETES_SERVICE_HOST=10.96.0.1
23PWD=/
24PASSWORD=123456

另外一个列子 使用envFrom ,一次导入configMap所有的数据到pod中

1apiVersion: v1
2kind: ConfigMap
3metadata:
4  name: maria-cm
5data:
6  DATABASE: 'db'
7  USER: 'wp'
8  PASSWORD: '123'
9  ROOT_PASSWORD: '123'
 1apiVersion: v1
 2kind: Pod
 3metadata:
 4  name: maria-pod
 5  labels:
 6    app: wordpress
 7    role: database
 8spec:
 9  containers:
10  - image: mariadb:10
11    name: maria
12    imagePullPolicy: IfNotPresent
13    ports:
14    - containerPort: 3306
15    envFrom:
16    - prefix: 'MARIADB_'
17      configMapRef:
18        name: maria-cm

使用了一个新的字段“envFrom”,这是因为 ConfigMap 里的信息比较多,如果用 env.valueFrom 一个个地写会非常麻烦,容易出错,而 envFrom 可以一次性地把 ConfigMap 里的字段全导入进 Pod,并且能够指定变量名的前缀(即这里的 MARIADB_),非常方便

如何以 Volume 的方式使用 ConfigMap/Secret

Kubernetes 为 Pod 定义了一个“Volume”的概念,可以翻译成是“存储卷”。如果把 Pod 理解成是一个虚拟机,那么 Volume 就相当于是虚拟机里的磁盘。

在 Pod 里挂载 Volume 很容易,只需要在“spec”里增加一个“volumes”字段,然后再定义卷的名字和引用的 ConfigMap/Secret 就可以了。要注意的是 Volume 属于 Pod,不属于容器,所以它和字段“containers”是同级的,都属于“spec”。

pod-env-from-volumn-cm-secret.yaml

 1apiVersion: v1
 2kind: Pod
 3metadata:
 4  name: vol-pod
 5spec:
 6  volumes: #定义pod的 存储卷
 7  - name: cm-vol
 8    configMap:
 9      name: info
10  - name: sec-vol
11    secret:
12      secretName: user
13  containers:
14  - volumeMounts:
15    - mountPath: /tmp/cm-items
16      name: cm-vol        # 使用pod名为cm-vol的存储卷 
17    - mountPath: /tmp/sec-items
18      name: sec-vol        # 使用pod名为sec-vol 的存储卷 
19    image: busybox
20    name: busy
21    imagePullPolicy: IfNotPresent
22    command: ["/bin/sleep", "300"]
1kubectl apply -f pod-env-from-volumn-cm-secret.yaml
2kubectl get pod
 1vagrant@master:~/k8syaml$ kubectl exec -it vol-pod -- sh
 2/ # cd /tmp/cm-items
 3/tmp/cm-items # ls
 4count     debug     greeting  path
 5/tmp/cm-items # ls -al
 6total 12
 7drwxrwxrwx    3 root     root          4096 Aug 27 03:15 .
 8drwxrwxrwt    1 root     root          4096 Aug 27 03:15 ..
 9drwxr-xr-x    2 root     root          4096 Aug 27 03:15 ..2022_08_27_03_15_30.3915883538
10lrwxrwxrwx    1 root     root            32 Aug 27 03:15 ..data -> ..2022_08_27_03_15_30.3915883538
11lrwxrwxrwx    1 root     root            12 Aug 27 03:15 count -> ..data/count
12lrwxrwxrwx    1 root     root            12 Aug 27 03:15 debug -> ..data/debug
13lrwxrwxrwx    1 root     root            15 Aug 27 03:15 greeting -> ..data/greeting
14lrwxrwxrwx    1 root     root            11 Aug 27 03:15 path -> ..data/path
15/tmp/cm-items # vim count
16sh: vim: not found
17/tmp/cm-items # cat count
18/tmp/cm-items # cat debug
19on/tmp/cm-items #
20/tmp/cm-items #
21/tmp/cm-items #

ConfigMap 和 Secret 都变成了目录的形式,而它们里面的 Key-Value 变成了一个个的文件,而文件名就是 Key。

因为这种形式上的差异,以 Volume 的方式来使用 ConfigMap/Secret,就和环境变量不太一样。环境变量用法简单,更适合存放简短的字符串,而 Volume 更适合存放大数据量的配置文件,在 Pod 里加载成文件后让应用直接读取使用

总结

  1. ConfigMap 记录了一些 Key-Value 格式的字符串数据,描述字段是“data”,不是“spec”。

  2. Secret 与 ConfigMap 很类似,也使用“data”保存字符串数据,但它要求数据必须是 Base64 编码,起到一定的保密效果。

  3. 在 Pod 的“env.valueFrom”字段中可以引用 ConfigMap 和 Secret,把它们变成应用可以访问的环境变量。

  4. 在 Pod 的“spec.volumes”字段中可以引用 ConfigMap 和 Secret,把它们变成存储卷,然后在“spec.containers.volumeMounts”字段中加载成文件的形式。

  5. ConfigMap 和 Secret 对存储数据的大小限制1mb,但小数据用环境变量比较适合,大数据应该用存储卷,可根据具体场景灵活应用。

修改了 ConfigMap/Secret 的 YAML,然后使用 kubectl apply 命令更新对象,那么 Pod 里关联的信息是否会同步更新呢?

实验后答案: 环境变量是pod启动时注入的,所以不会改,volume的方式会改,两种方式不一样

Deployment

介绍

Deployment 实际上并不“持有”Pod 对象,它只是帮助 Pod 对象能够有足够的副本数量运行

1kubectl api-resources
2NAME         SHORTNAMES   APIVERSION   NAMESPACED   KIND
3deployments  deploy       apps/v1      true        Deployment
1kubectl create deploy ngx-dep --image=nginx:alpine --dry-run=client -o yaml

deploy.yml

 1apiVersion: apps/v1
 2kind: Deployment
 3metadata:
 4  labels:
 5    app: ngx-dep
 6  name: ngx-dep
 7  
 8spec:
 9  replicas: 2 # 副本数量
10  selector:   # 筛选”出要被 Deployment 管理的 Pod 对象
11    matchLabels:
12      app: ngx-dep
13      
14  template:
15    metadata:
16      labels:
17        app: ngx-dep
18    spec:
19      containers:
20      - image: nginx:alpine
21        name: nginx
1vagrant@master:/home/www/k8syaml$ kubectl get deploy
2NAME      READY   UP-TO-DATE   AVAILABLE   AGE
3ngx-dep   2/2     2            2           5s
1READY 表示运行的 Pod 数量,前面的数字是当前数量,后面的数字是期望数量,所以“2/2”的意思就是要求有两个 Pod 运行,现在已经启动了两个 Pod。
2UP-TO-DATE 指的是当前已经更新到最新状态的 Pod 数量。因为如果要部署的 Pod 数量很多或者 Pod 启动比较慢,Deployment 完全生效需要一个过程,UP-TO-DATE 就表示现在有多少个 Pod 已经完成了部署,达成了模板里的“期望状态”。
3AVAILABLE 要比 READY、UP-TO-DATE 更进一步,不仅要求已经运行,还必须是健康状态,能够正常对外提供服务,它才是我们最关心的 Deployment 指标。
4最后一个 AGE 就简单了,表示 Deployment 从创建到现在所经过的时间,也就是运行的时间。

应用伸缩

kubectl scale 是专门用于实现“扩容”和“缩容”的命令

1kubectl scale --replicas=5 deploy ngx-dep

kubectl scale 是命令式操作,扩容和缩容只是临时的措施,如果应用需要长时间保持一个确定的 Pod 数量,最好还是编辑 Deployment 的 YAML 文件,改动“replicas”,再以声明式的 kubectl apply 修改对象的状态

kubectl get 命令的时候,加上参数 -l,使用 ==、!=、in、notin 的表达式,就能够很容易地用“标签”筛选

通过标签找pod

1kubectl get pod -l app=nginx
2kubectl get pod -l 'app in (ngx, nginx, ngx-dep)'

DaemonSet

为什么要有daemonSet?

网络应用(如 kube-proxy),必须每个节点都运行一个 Pod,否则节点就无法加入 Kubernetes 网络。

监控应用(如 Prometheus),必须每个节点都有一个 Pod 用来监控节点的状态,实时上报信息。

日志应用(如 Fluentd),必须在每个节点上运行一个 Pod,才能够搜集容器运行时产生的日志数据。

安全应用,同样的,每个节点都要有一个 Pod 来执行安全审计、入侵检查、漏洞扫描等工作。

这个用deployment 会出现 部署的pod 在节点“偏移”,然后你需要他固定在某个节点上 ,所以用deployment不适合。

所以,Kubernetes 就定义了新的 API 对象 DaemonSet,它在形式上和 Deployment 类似,都是管理控制 Pod,但管理调度策略却不同。DaemonSet 的目标是在集群的每个节点上运行且仅运行一个 Pod,就好像是为节点配上一只“看门狗”,忠实地“守护”着节点,这就是 DaemonSet 名字的由来

Kubernetes 不提供自动创建 DaemonSet YAML 样板的功能

https://kubernetes.io/zh/docs/concepts/workloads/controllers/daemonset/ ,抄这个

或者直接生成一个 deployment 的模板,kind 改成 DaemonSet,再删除 spec.replicas

 1apiVersion: apps/v1
 2kind: DaemonSet
 3metadata:
 4  name: redis-ds
 5  labels:
 6    app: redis-ds
 7spec:
 8  selector:
 9    matchLabels:
10      name: redis-ds
11  template:
12    metadata:
13      labels:
14        name: redis-ds
15    spec:
16      containers:
17      - image: redis:5-alpine
18        name: redis
19        ports:
20        - containerPort: 6379

这样的daemonSet ,部署后,你只会发现在work 节点上部署了, 在master 上 没有部署

污点(taint)和容忍度(toleration)

“污点”是 Kubernetes 节点的一个属性,它的作用也是给节点“贴标签”,但为了不和已有的 labels 字段混淆,就改成了 taint

taint 理解为 节点的 标签

容忍度(toleration)能否“容忍”污点, Pod 的“容忍度。

Pod 也脾气各异,有的“洁癖”很严重,不能容忍“污点”,只能挑选“干净”的节点;而有的 Pod 则比较“大大咧咧”,要求不那么高,可以适当地容忍一些小“污点”

“污点”和“容忍度”倒是有点像是一个“相亲”的过程。Pod 就是一个挑剔的“甲方”,而“乙方”就是集群里的各个节点

1vagrant@master:/home/www/k8syaml$ kubectl describe node master  | grep Taints
2Taints:             node-role.kubernetes.io/master:NoSchedule
3
4
5# master  污点是 默认不调度

第一种方法是去掉 Master 节点上的 taint,让 Master 变得和 Worker 一样“纯洁无瑕”,DaemonSet 自然就不需要再区分 Master/Worker。

1kubectl taint node master node-role.kubernetes.io/master:NoSchedule-

第二种方法,为 Pod 添加字段 tolerations,让它能够“容忍”某些“污点”,就可以在任意的节点上运行了

tolerations 是一个数组,里面可以列出多个被“容忍”的“污点”,需要写清楚“污点”的名字、效果。比较特别是要用 operator 字段指定如何匹配“污点”,一般我们都使用 Exists,也就是说存在这个名字和效果的“污点”

ds.yaml

 1apiVersion: apps/v1
 2kind: DaemonSet
 3metadata:
 4  name: redis-ds
 5  labels:
 6    app: redis-ds
 7spec:
 8  selector:
 9    matchLabels:
10      name: redis-ds
11  template:
12    metadata:
13      labels:
14        name: redis-ds
15    spec:
16      containers:
17      - image: redis:5-alpine
18        name: redis
19        ports:
20        - containerPort: 6379
21      tolerations:     #添加容忍度
22      - key: node-role.kubernetes.io/master
23        effect: NoSchedule
24        operator: Exists

这时就发现,master和node 都已经有了

1kubectl apply -f  ds.yaml
2vagrant@master:/home/www/k8syaml$ kubectl get pod -o wide | grep ds
3redis-ds-msx8n            1/1     Running     0                 45s     10.10.0.18    master   <none>           <none>
4redis-ds-xxklj            1/1     Running     0                 45s     10.10.1.2     node2    <none>           <none>

“容忍度”并不是 DaemonSet 独有的概念,而是从属于 Pod,所以理解了“污点”和“容忍度”之后,你可以在 Job/CronJob、Deployment 里为它们管理的 Pod 也加上 tolerations,从而能够更灵活地调度应用。

至于都有哪些污点、污点有哪些效果我就不细说了,Kubernetes 官网文档(https://kubernetes.io/zh/docs/concepts/scheduling-eviction/taint-and-toleration/)

什么是静态 Pod

“静态 Pod”非常特殊,它不受 Kubernetes 系统的管控,不与 apiserver、scheduler 发生关系,所以是“静态”的。

“静态 Pod”的 YAML 文件默认都存放在节点的 /etc/kubernetes/manifests 目录下,它是 Kubernetes 的专用目录。

Kubernetes 的 4 个核心组件 apiserver、etcd、scheduler、controller-manager 原来都以静态 Pod 的形式存在的,这也是为什么它们能够先于 Kubernetes 集群启动的原因。

如果你有一些 DaemonSet 无法满足的特殊的需求,可以考虑使用静态 Pod,编写一个 YAML 文件放到这个目录里,节点的 kubelet 会定期检查目录里的文件,发现变化就会调用容器运行时创建或者删除静态 Pod。

必须在节点上纯手动部署,应当慎用

Service

Pod 的 IP 地址老是变来变去,客户端该怎么访问呢?

这里 Service 使用了 iptables 技术,每个节点上的 kube-proxy 组件自动维护 iptables 规则,客户不再关心 Pod 的具体地址,只要访问 Service 的固定 IP 地址,Service 就会根据 iptables 规则转发请求给它管理的多个 Pod,是典型的负载均衡架构

它还有另外两种实现技术:性能更差的 userspace 和性能更好的 ipvs

它的简称是svc,apiVersion 是 v1。注意,这说明它与 Pod 一样,属于 Kubernetes 的核心对象,不关联业务应用,与 Job、Deployment 是不同的

kubectl expose,也许 Kubernetes 认为“expose”能够更好地表达 Service“暴露”服务地址

1export out="--dry-run=client -o yaml"
2kubectl expose deploy ngx-dep --port=80 --target-port=80 $out

svc.yaml

 1apiVersion: v1
 2kind: Service
 3metadata:
 4  name: ngx-svc
 5  
 6spec:
 7  selector:
 8    app: ngx-dep
 9    
10  ports:
11  - port: 80       
12    targetPort: 80 #容器端口
13    protocol: TCP
14
15# 默认是clusterIp 类型 

selector 和 Deployment/DaemonSet 里的作用是一样的,用来过滤出要代理的那些 Pod。因为我们指定要代理 Deployment,所以 Kubernetes 就为我们自动填上了 ngx-dep 的标签,会选择这个 Deployment 对象部署的所有 Pod。

ports 里面的三个字段分别表示外部端口、内部端口和使用的协议,在这里就是内外部都使用 80 端口,协议是 TCP。

如何在 Kubernetes 里使用 Service

nginx-configmap.yaml

 1apiVersion: v1
 2kind: ConfigMap
 3metadata:
 4  name: ngx-conf
 5data:
 6  default.conf: |
 7    server {
 8      listen 80;
 9      location / {
10        default_type text/plain;
11        return 200
12          'srv : $server_addr:$server_port\nhost: $hostname\nuri : $request_method $host $request_uri\ndate: $time_iso8601\n';
13      }
14    }    
1kubectl apply -f nginx-configmap.yaml

deploy.yaml

 1apiVersion: apps/v1
 2kind: Deployment
 3metadata:
 4  name: ngx-dep
 5spec:
 6  replicas: 2
 7  selector:
 8    matchLabels:
 9      app: ngx-dep
10  template:
11    metadata:
12      labels:
13        app: ngx-dep
14    spec:
15      volumes:
16      - name: ngx-conf-vol
17        configMap:
18          name: ngx-conf
19      containers:
20      - image: nginx:alpine
21        name: nginx
22        ports:
23        - containerPort: 80
24        volumeMounts:
25        - mountPath: /etc/nginx/conf.d
26          name: ngx-conf-vol
 1kubectl apply -f deploy.yaml
 2kubectl exec -it ngx-dep-6796688696-kwxh5 -- sh
 3/etc/nginx/conf.d # cat default.conf
 4server {
 5  listen 80;
 6  location / {
 7    default_type text/plain;
 8    return 200
 9      'srv : $server_addr:$server_port\nhost: $hostname\nuri : $request_method $host $request_uri\ndate: $time_iso8601\n';
10  }
11}
12
13# apply 上面的svc.yaml
14kubectl apply -f svc.yaml
 1vagrant@master:/home/www/k8syaml$ kubectl describe svc ngx-svc
 2Name:              ngx-svc
 3Namespace:         default
 4Labels:            <none>
 5Annotations:       <none>
 6Selector:          app=ngx-dep
 7Type:              ClusterIP
 8IP Family Policy:  SingleStack
 9IP Families:       IPv4
10IP:                10.108.80.81
11IPs:               10.108.80.81
12Port:              <unset>  80/TCP
13TargetPort:        80/TCP
14Endpoints:         10.10.1.93:80,10.10.1.94:80
15Session Affinity:  None
16Events:            <none>
 1kubectl exec -it ngx-dep-6796688696-kwxh5 -- sh
 2#3个ip curl 都能访问通
 3
 4/ # curl 10.10.1.93
 5srv : 10.10.1.93:80
 6host: ngx-dep-6796688696-kwxh5
 7uri : GET 10.10.1.93 /
 8date: 2022-08-30T15:39:53+00:00
 9/ # curl 10.10.1.94
10srv : 10.10.1.94:80
11host: ngx-dep-6796688696-zldf8
12uri : GET 10.10.1.94 /
13date: 2022-08-30T15:39:54+00:00
14
15/ # ping 10.108.80.81 但是这个ip 是拼不通的
16PING 10.108.80.81 (10.108.80.81): 56 data bytes

会发现根本 ping 不通,因为 Service 的 IP 地址是“虚”的,只用于转发流量,所以 ping 无法得到回应数据包,也就失败了

删除pod 后, 新建的pod 这个service 也是能访问通的

由于 Pod 被 Deployment 对象管理,删除后会自动重建,而 Service 又会通过 controller-manager 实时监控 Pod 的变化情况,所以就会立即更新它代理的 IP 地址。

 1vagrant@master:/home/www/k8syaml$ kubectl describe svc ngx-svc
 2Name:              ngx-svc
 3Namespace:         default
 4Labels:            <none>
 5Annotations:       <none>
 6Selector:          app=ngx-dep
 7Type:              ClusterIP
 8IP Family Policy:  SingleStack
 9IP Families:       IPv4
10IP:                10.108.80.81 #没有变化
11IPs:               10.108.80.81
12Port:              <unset>  80/TCP
13TargetPort:        80/TCP
14Endpoints:         10.10.1.113:80,10.10.1.93:80       #10.10.1.94这个ip 没了 变成了 10.10.1.113
15Session Affinity:  None
16Events:            <none>

如何以域名的方式使用 Service

Service 对象的 IP 地址是静态的,保持稳定,这在微服务里确实很重要,不过数字形式的 IP 地址用起来还是不太方便。这个时候 Kubernetes 的 DNS 插件就派上了用处,它可以为 Service 创建易写易记的域名,让 Service 更容易使用

命名空间

1vagrant@master:/home/www/k8syaml$ kubectl get ns
2NAME              STATUS   AGE
3default           Active   11d
4kube-flannel      Active   11d
5kube-node-lease   Active   11d
6kube-public       Active   11d
7kube-system       Active   11d

Kubernetes 有一个默认的名字空间,叫“default”,如果不显式指定,API 对象都会在这个“default”名字空间里。而其他的名字空间都有各自的用途,比如“kube-system”就包含了 apiserver、etcd 等核心组件的 Pod。

因为 DNS 是一种层次结构,为了避免太多的域名导致冲突,Kubernetes 就把名字空间作为域名的一部分,减少了重名的可能性。

Service 对象的域名完全形式是“对象. 名字空间.svc.cluster.local”,但很多时候也可以省略后面的部分,直接写“对象. 名字空间”甚至“对象名”就足够了,默认会使用对象所在的名字空间(比如这里就是 default)。

 1vagrant@master:/home/www/k8syaml$ kubectl exec -it ngx-dep-6796688696-2g2wb -- sh
 2/ # curl ngx-svc.default     
 3srv : 10.10.1.127:80
 4host: ngx-dep-6796688696-lqtv8
 5uri : GET ngx-svc.default /
 6date: 2022-08-30T16:26:28+00:00
 7/ # curl ngx-svc
 8srv : 10.10.1.127:80
 9host: ngx-dep-6796688696-lqtv8
10uri : GET ngx-svc /
11date: 2022-08-30T16:26:34+00:00
12/ # cat /etc/resolv.conf
13nameserver 10.96.0.10
14search default.svc.cluster.local svc.cluster.local cluster.local
15options ndots:5
16/ # curl ngx-svc.default.svc.cluster.local
17srv : 10.10.1.127:80
18host: ngx-dep-6796688696-lqtv8
19uri : GET ngx-svc.default.svc.cluster.local /
20date: 2022-08-30T16:27:05+00:00
21
22# 3种 都可以用

dns问题

是因为虚拟机用了2张网卡导致的

解决1.

 1#centos系统
 2vi /etc/sysconfig/kubelet
 3KUBELET_EXTRA_ARGS=--node-ip=192.168.56.xxx
 4
 5systemctl restart kubelet
 6
 7#ubuntu系统,在ExecStart之前添加一行 ,每个节点都要配置并重启
 8vi /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
 9Environment="KUBELET_EXTRA_ARGS=--node-ip=192.168.56.xxx"
10ExecStart=
11ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS
  1. fannel 选错网卡问题

    kube-fannel.yaml

    1args:
    2- --ip-masq
    3- --kube-subnet-mgr
    4- --iface=eth1        #增加 node-ip 的网卡, 
    

如何让 Service 对外暴露服务

Service 对象有一个关键字段“type”,表示 Service 是哪种类型的负载均衡。前面我们看到的用法都是对集群内部 Pod 的负载均衡,所以这个字段的值就是默认的“ClusterIP”,Service 的静态 IP 地址只能在集群内访问。

除了“ClusterIP”,Service 还支持其他三种类型,分别是“ExternalName”“LoadBalancer”“NodePort”。不过前两种类型一般由云服务商提供,我们的实验环境用不到,所以接下来就重点看“NodePort”这个类型

kubectl expose 的时候加上参数 –type=NodePort

让我们修改一下 Service 的 YAML 文件,加上字段“type”:

 1apiVersion: v1
 2kind: Service
 3metadata:
 4  name: ngx-svc
 5  
 6spec:
 7  selector:
 8    app: ngx-dep
 9  type: NodePort  
10  ports:
11  - port: 80       
12    targetPort: 80 #容器端口
13    protocol: TCP
 1vagrant@master:/home/www/k8syaml$ kubectl get svc
 2NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
 3kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP        11d
 4ngx-svc      NodePort    10.102.124.149   <none>        80:32742/TCP   37m
 5
 6# 32742 节点的端口 
 7vagrant@master:/home/www/k8syaml$ curl http://10.102.124.149
 8srv : 10.10.1.130:80
 9host: ngx-dep-6796688696-2g2wb
10uri : GET 10.102.124.149 /
11date: 2022-08-30T16:33:38+00:00

就会看到“TYPE”变成了“NodePort”,而在“PORT”列里的端口信息也不一样,除了集群内部使用的“80”端口,还多出了一个“30651”端口,这就是 Kubernetes 在节点上为 Service 创建的专用映射端口。

image-20220831003627100

service 的缺点

第一个缺点是它的端口数量很有限。Kubernetes 为了避免端口冲突,默认只在“30000~32767”这个范围内随机分配,只有 2000 多个,而且都不是标准端口号,这对于具有大量业务应用的系统来说根本不够用。

第二个缺点是它会在每个节点上都开端口,然后使用 kube-proxy 路由到真正的后端 Service,这对于有很多计算节点的大集群来说就带来了一些网络通信成本,不是特别经济。

第三个缺点,它要求向外界暴露节点的 IP 地址,这在很多时候是不可行的,为了安全还需要在集群外再搭一个反向代理,增加了方案的复杂度。

service 小节

Pod 的生命周期很短暂,会不停地创建销毁,所以就需要用 Service 来实现负载均衡,它由 Kubernetes 分配固定的 IP 地址,能够屏蔽后端的 Pod 变化。

Service 对象使用与 Deployment、DaemonSet 相同的“selector”字段,选择要代理的后端 Pod,是松耦合关系。

基于 DNS 插件,我们能够以域名的方式访问 Service,比静态 IP 地址更方便。

名字空间是 Kubernetes 用来隔离对象的一种方式,实现了逻辑上的对象分组,Service 的域名里就包含了名字空间限定。

Service 的默认类型是“ClusterIP”,只能在集群内部访问,如果改成“NodePort”,就会在节点上开启一个随机端口号,让外界也能够访问内部的服务。

Ingress

为什么要有 Ingress

Service 的功能和运行机制,它本质上就是一个由 kube-proxy 控制的四层负载均衡,在 TCP/IP 协议栈上转发流量

service 在四层上的负载均衡功能还是太有限了,只能够依据 IP 地址和端口号做一些简单的判断和组合,而我们现在的绝大多数应用都是跑在七层的 HTTP/HTTPS 协议上的,有更多的高级路由条件,比如主机名、URI、请求头、证书等等,而这些在 TCP/IP 网络栈里是根本看不见的。

Service 还有一个缺点,它比较适合代理集群内部的服务。如果想要把服务暴露到集群外部,就只能使用 NodePort 或者 LoadBalancer 这两种方式,而它们都缺乏足够的灵活性,难以管控

Kubernetes 还是沿用了 Service 的思路,既然 Service 是四层的负载均衡,那么我再引入一个新的 API 对象,在七层上做负载均衡是不是就可以了呢?

不过除了七层负载均衡,这个对象还应该承担更多的职责,也就是作为流量的总入口,统管集群的进出口数据,“扇入”“扇出”流量(也就是我们常说的“南北向”),让外部用户能够安全、顺畅、便捷地访问内部服务

所以,这个 API 对象就顺理成章地被命名为 Ingress,意思就是集群内外边界上的入口。

为什么要有 Ingress Controller

Ingress 可以说是在七层上另一种形式的 Service,它同样会代理一些后端的 Pod,也有一些路由规则来定义流量应该如何分配、转发,只不过这些规则都使用的是 HTTP/HTTPS 协议。

你应该知道,Service 本身是没有服务能力的,它只是一些 iptables 规则,真正配置、应用这些规则的实际上是节点里的 kube-proxy 组件。如果没有 kube-proxy,Service 定义得再完善也没有用。

同样的,Ingress 也只是一些 HTTP 路由规则的集合,相当于一份静态的描述文件,真正要把这些规则在集群里实施运行,还需要有另外一个东西,这就是 Ingress Controller,它的作用就相当于 Service 的 kube-proxy,能够读取、应用 Ingress 规则,处理、调度流量。

按理来说,Kubernetes 应该把 Ingress Controller 内置实现,作为基础设施的一部分,就像 kube-proxy 一样。

不过 Ingress Controller 要做的事情太多,与上层业务联系太密切,所以 Kubernetes 把 Ingress Controller 的实现交给了社区,任何人都可以开发 Ingress Controller,只要遵守 Ingress 规则就好。

Kubernetes 里应用得最广泛的 Ingress Controller: 是基于nginx 实现的

社区的 Kubernetes Ingress Controller(https://github.com/kubernetes/ingress-nginx)

Nginx 公司自己的 Nginx Ingress Controller(https://github.com/nginxinc/kubernetes-ingress)

还有基于 OpenResty 的 Kong Ingress Controller(https://github.com/Kong/kubernetes-ingress-controller)等等

Nginx 公司的开发实现是下载量最多的 Ingress Controller

为什么要有 IngressClass

那么到现在,有了 Ingress 和 Ingress Controller,我们是不是就可以完美地管理集群的进出流量了呢?

最初 Kubernetes 也是这么想的,一个集群里有一个 Ingress Controller,再给它配上许多不同的 Ingress 规则,应该就可以解决请求的路由和分发问题了。

但随着 Ingress 在实践中的大量应用,很多用户发现这种用法会带来一些问题,比如:

由于某些原因,项目组需要引入不同的 Ingress Controller,但 Kubernetes 不允许这样做;

Ingress 规则太多,都交给一个 Ingress Controller 处理会让它不堪重负;

多个 Ingress 对象没有很好的逻辑分组方式,管理和维护成本很高;

集群里有不同的租户,他们对 Ingress 的需求差异很大甚至有冲突,无法部署在同一个 Ingress Controller 上。

所以,Kubernetes 就又提出了一个 Ingress Class 的概念,让它插在 Ingress 和 Ingress Controller 中间,作为流量规则和控制器的协调人,解除了 Ingress 和 Ingress Controller 的强绑定关系。

现在,Kubernetes 用户可以转向管理 Ingress Class,用它来定义不同的业务逻辑分组,简化 Ingress 规则的复杂度。比如说,我们可以用 Class A 处理博客流量、Class B 处理短视频流量、Class C 处理购物流量。

如何使用 YAML 描述 Ingress/Ingress Class

创建一个ingress

1kubectl create ing ngx-ing --rule="ngx.test/=ngx-svc:80" --class=ngx-ink --dry-run=client -o yaml

ingress.yml

 1apiVersion: networking.k8s.io/v1
 2kind: Ingress
 3metadata:
 4  creationTimestamp: null
 5  name: ngx-ing
 6spec:
 7  ingressClassName: ngx-ink
 8  rules:
 9  - host: ngx.test
10    http:
11      paths:
12      - backend:
13          service:
14            name: ngx-svc
15            port:
16              number: 80
17        path: /
18        pathType: Exact

在这份 Ingress 的 YAML 里,有两个关键字段:“ingressClassName”和“rules”,分别对应了命令行参数,含义还是比较好理解的。

只是“rules”的格式比较复杂,嵌套层次很深。不过仔细点看就会发现它是把路由规则拆散了,有 host 和 http path,在 path 里又指定了路径的匹配方式,可以是精确匹配(Exact)或者是前缀匹配(Prefix),再用 backend 来指定转发的目标 Service 对象。

不过我个人觉得,Ingress YAML 里的描述还不如 kubectl create 命令行里的 –rule 参数来得直观易懂,而且 YAML 里的字段太多也很容易弄错,建议你还是让 kubectl 来自动生成规则,然后再略作修改比较好。

Ingress Class 本身并没有什么实际的功能,只是起到联系 Ingress 和 Ingress Controller 的作用,所以它的定义非常简单

在“spec”里只有一个必需的字段“controller”,表示要使用哪个 Ingress Controller,具体的名字就要看实现文档了。

比如,如果我要用 Nginx 开发的 Ingress Controller,那么就要用名字“nginx.org/ingress-controller”:

ingress-class.yaml

1apiVersion: networking.k8s.io/v1
2kind: IngressClass
3metadata:
4  name: ngx-ink
5spec:
6  controller: nginx.org/ingress-controller

Ingress 和 Service、Ingress Class 的关系图

把上面的2个yaml apply一下

 1kubectl apply -f ingress.yml
 2kubectl apply -f ingress-class.yaml
 3kubectl get ingressclass
 4kubectl get ing
 5kubectl describe ing ngx-ing
 6
 7
 8vagrant@master:/home/www/k8syaml$ kubectl get ingressclass
 9NAME      CONTROLLER                     PARAMETERS   AGE
10ngx-ink   nginx.org/ingress-controller   <none>       63m
11vagrant@master:/home/www/k8syaml$ kubectl get ing
12NAME      CLASS     HOSTS      ADDRESS   PORTS   AGE
13ngx-ing   ngx-ink   ngx.test             80      64m

如何在 Kubernetes 里使用 Ingress Controller

在 GitHub 上找到 Nginx Ingress Controller 的项目(https://github.com/nginxinc/kubernetes-ingress),因为它以 Pod 的形式运行在 Kubernetes 里,所以同时支持 Deployment 和 DaemonSet 两种部署方式。这里我选择的是 Deployment,相关的 YAML 也都在我们课程的项目(https://github.com/chronolaw/k8s_study/tree/master/ingress)里复制了一份。

Nginx Ingress Controller 的安装略微麻烦一些,有很多个 YAML 需要执行,但如果只是做简单的试验,就只需要用到 4 个 YAML:

1kubectl apply -f common/ns-and-sa.yaml
2kubectl apply -f rbac/rbac.yaml
3kubectl apply -f common/nginx-config.yaml
4kubectl apply -f common/default-server-secret.yaml

这样还是不行, setup.sh 把 common/crds 下面的也执行一遍要

前两条命令为 Ingress Controller 创建了一个独立的名字空间“nginx-ingress”,还有相应的账号和权限,这是为了访问 apiserver 获取 Service、Endpoint 信息用的;后两条则是创建了一个 ConfigMap 和 Secret,用来配置 HTTP/HTTPS 服务。

部署 Ingress Controller 不需要我们自己从头编写 Deployment,Nginx 已经为我们提供了示例 YAML,但创建之前为了适配我们自己的应用还必须要做几处小改动:

metadata 里的 name 要改成自己的名字,比如 ngx-kic-dep。

spec.selector 和 template.metadata.labels 也要修改成自己的名字,比如还是用 ngx-kic-dep。

containers.image 可以改用 apline 版本,加快下载速度,比如 nginx/nginx-ingress:2.2-alpine。

最下面的 args 要加上 -ingress-class=ngx-ink,也就是前面创建的 Ingress Class 的名字,这是让 Ingress Controller 管理 Ingress 的关键。

修改完之后,Ingress Controller 的 YAML 大概是这个样子:

https://github.com/nginxinc/kubernetes-ingress/blob/main/deployments/deployment/nginx-ingress.yaml

deploy-nginx-ingress-controller.yaml

(这个也可以是 DaemonSet 类型运行) 。

 1apiVersion: apps/v1
 2kind: Deployment
 3metadata:
 4  name: ngx-kic-dep
 5  namespace: nginx-ingress
 6spec:
 7  replicas: 1
 8  selector:
 9    matchLabels:
10      app: ngx-kic-dep
11  template:
12    metadata:
13      labels:
14        app: ngx-kic-dep
15    spec:
16      serviceAccountName: nginx-ingress
17      containers:
18      - image: nginx/nginx-ingress:2.2-alpine
19        imagePullPolicy: IfNotPresent
20        name: nginx-ingress
21        ports:
22        - name: http
23          containerPort: 80
24        - name: https
25          containerPort: 443
26        - name: readiness-port
27          containerPort: 8081
28        - name: prometheus
29          containerPort: 9113
30        readinessProbe:
31          httpGet:
32            path: /nginx-ready
33            port: readiness-port
34          periodSeconds: 1
35        resources:
36          requests:
37            cpu: "100m"
38            memory: "128Mi"
39        securityContext:
40          allowPrivilegeEscalation: true
41          runAsUser: 101 #nginx
42          runAsNonRoot: true
43          capabilities:
44            drop:
45            - ALL
46            add:
47            - NET_BIND_SERVICE
48        env:
49        - name: POD_NAMESPACE
50          valueFrom:
51            fieldRef:
52              fieldPath: metadata.namespace
53        - name: POD_NAME
54          valueFrom:
55            fieldRef:
56              fieldPath: metadata.name
57        args:
58          - -nginx-configmaps=$(POD_NAMESPACE)/nginx-config
59          - -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret
60          - -ingress-class=ngx-ink
1kubectl apply -f deploy-nginx-ingress-controller.yaml

有了 Ingress Controller,这些 API 对象的关联就更复杂了,你可以用下面的这张图来看出它们是如何使用对象名字联系起来的:

1kubectl get deploy -n nginx-ingress
2kubectl get pod -n nginx-ingress
3
4vagrant@master:/home/www/k8syaml/ingress$ kubectl get deploy -n nginx-ingress
5NAME          READY   UP-TO-DATE   AVAILABLE   AGE
6ngx-kic-dep   1/1     1            1           11m
7vagrant@master:/home/www/k8syaml/ingress$ kubectl get pod -n nginx-ingress
8NAME                          READY   STATUS    RESTARTS   AGE
9ngx-kic-dep-67c8d4468-z796c   1/1     Running   0          111s

现在 Ingress Controller 就算是运行起来了。

不过还有最后一道工序,因为 Ingress Controller 本身也是一个 Pod,想要向外提供服务还是要依赖于 Service 对象。所以你至少还要再为它定义一个 Service,使用 NodePort 或者 LoadBalancer 暴露端口,才能真正把集群的内外流量打通。

kubectl port-forward,它可以直接把本地的端口映射到 Kubernetes 集群的某个 Pod 里,在测试验证的时候非常方便。

下面这条命令就把本地的 8080 端口映射到了 Ingress Controller Pod 的 80 端口:

1kubectl port-forward -n nginx-ingress ngx-kic-dep-67c8d4468-z796c 8080:80 &
2[1] 33243
 1vagrant@master:/home/www/k8syaml/ingress$ sudo vim /etc/hosts
 2127.0.0.1 ngx.test
 3
 4vagrant@master:/home/www/k8syaml/ingress$ curl http://ngx.test:8080
 5Handling connection for 8080
 6srv : 10.10.1.204:80
 7host: ngx-dep-6796688696-lqtv8
 8uri : GET ngx.test /
 9date: 2022-09-14T15:52:56+00:00
10vagrant@master:/home/www/k8syaml/ingress$ curl http://ngx.test:8080
11Handling connection for 8080
12srv : 10.10.1.200:80
13host: ngx-dep-6796688696-2g2wb
14uri : GET ngx.test /
15date: 2022-09-14T15:52:57+00:00
16
17# 也可以curl --resolve ngx.test:8080:127.0.0.1 http://ngx.test:8080 
18# 也可以使用 --resolve 参数,指定域名的解析规则,比如在这里我就把“ngx.test”强制解析到“127.0.0.1”

小节

Service 是四层负载均衡,能力有限,所以就出现了 Ingress,它基于 HTTP/HTTPS 协议定义路由规则。

Ingress 只是规则的集合,自身不具备流量管理能力,需要 Ingress Controller 应用 Ingress 规则才能真正发挥作用。

Ingress Class 解耦了 Ingress 和 Ingress Controller,我们应当使用 Ingress Class 来管理 Ingress 资源。

最流行的 Ingress Controller 是 Nginx Ingress Controller,它基于经典反向代理软件 Nginx。

再补充一点,目前的 Kubernetes 流量管理功能主要集中在 Ingress Controller 上,已经远不止于管理“入口流量”了,它还能管理“出口流量”,也就是 egress,甚至还可以管理集群内部服务之间的“东西向流量”。

此外,Ingress Controller 通常还有很多的其他功能,比如 TLS 终止、网络应用防火墙、限流限速、流量拆分、身份认证、访问控制等等,完全可以认为它是一个全功能的反向代理或者网关。

四层架构简单,无需解析消息内容,在网络吞吐量及处理性能上高于七层。 而七层负载优势在于功能多,控制灵活强大。

ingress controller 名字比较长,有时候缩写成 IC 或者KIC

为了提升路由效率,降低网络成本,ingress controller 通常不会走 service 流量转发,而是通过apiserver 直接获得service代理的pod地址,从而绕过了service 的iptables 规则。

极客时间k8s入门实战

CronJob控制器中的一些绕坑指南

pod dns 问题

error: unable to upgrade connection: pod does not exist 解决方案 每个节点都设置一遍