第零讲 环境准备

使用 virtualbox 安装 ubuntu 虚拟环境

个人采坑记录

共享粘贴板设置

配置了双向粘贴版 和 安装 apt install virtualbox-guest-utils 还是不行, 每次得手动启动 VBoxClient --clipborad. 通过 df -h 查看目录找到 挂载的脚步 在 /media/ubuntu/VBox_GAs_6.1.34/ 目录 后面卸载 virtualbox-guest-utils, 运行 sh /media/ubuntu/VBox_GAs_6.1.34/VBoxLinuxAdditions.run 安装才可以

第一讲 初始容器

sudo usermod -aG docker ${USER} 将当前用户加入 docker 组,然后重启, 否则需要 root 权限。

docker version 查看客户端和服务端版本信息

docker info 查看服务端系统信息

第二讲 被隔离的进程

容器就是一个特殊的隔离环境,它能够让进程只看到这个环境里的有限信息。

隔离实现基于 Linux 提供的三种技术:

  • namcespace:资源隔离
  • cgroup:配额限制
  • chroot:文件系统根目录限制

第三讲 docker 实用命令

常用镜像操作命令

image_cmd

常用容器操作命令

container_cmd

第四讲 Dockerfile 编写

镜像是由许多只读层堆叠起来的

image_layer

可以通过 docker inspect命令查看镜像的分层信息

image_layer2

示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16

# Dockerfile
# docker build -t ngx-app .
# docker build -t ngx-app:1.0 .

ARG IMAGE_BASE="nginx"
ARG IMAGE_TAG="1.21-alpine"

FROM ${IMAGE_BASE}:${IMAGE_TAG}

COPY ./default.conf /etc/nginx/conf.d/

RUN cd /usr/share/nginx/html \
    && echo "hello nginx" > a.txt

EXPOSE 8081 8082 8083

第五讲 镜像仓库

镜像仓库 registry 是提供镜像服务的网站,如上传、下载镜像等

Docker Hub 是当前最大的镜像仓库,上面有官方镜像、认证镜像、非官方镜像等

名字规则: user/image:tag

离线环境可以使用归档命令来共享

1
2
3
docker save ngx-app:latest -o ngx.tar

docker load -i ngx.tar

第六讲 容器如何如外接互通

如何拷贝容器内数据

  • 宿主机到容器
1
docker cp a.txt 062:/tmp
  • 容器到宿主机
1
docker cp 062:/tmp/a.txt ./b.txt

如何共享文件目录

volume 挂载

1
docker run -v host_path:container_path image:tag

如何网络互通

host 模式

1
docker run -d --rm --net=host  nginx:alpine

对比 ip addr命令在宿主机和容器的结果,可以发现是相同的

bridge 模式

桥接模式类似现实世界的交换机,不过是由软件虚拟出来的, 容器和宿主机通过虚拟网卡接入网桥 docker0,收发网络包

bridge_docker0

1
docker run -d --rm --net=bridge  nginx:alpine

使用 -p host_port:container_port 映射宿主机和容器端口

第九讲 本机搭建 k8s 环境

使用 minikube

启动 minikube

1
minikube start --kubernetes-version=v1.23.3 --image-mirror-country=cn

启动容器服务

1
2
3
alias kubectl="minikube kubectl --"

kubectl run ngx --image=nginx:alpine

第十讲 k8s 工作机制

k8s_architecture

k8s 有许多模块组成,这些模块可分为组件(component)和插件(addon)两类

master 组件

master

apiserver

apiserver 是 k8s 系统唯一入口,对外提供了一些列 RESTful API,其他组件 只能和它直接通信, 相当于联络员

etcd

etcd 用来持久化存储系统里各种资源对象和状态,相当于配置管理员。 其他组件想读写 etcd 的数据必须通过 apiserver

scheduler

scheduler 负责容器的编排工作,检查阶段的资源状态,把 pod 调度到 最合适的节点运行, 相当于部署人员。

controller-manager

controller-manager 负责维护容器和节点等资源状态,实现故障检测、服务迁移、应用伸缩等功能, 相当于监控运维人员。

Node 组件

node

kubelet

kubelet 负责管理 Node 相关绝大部分操作, Node 上只有它与 apiserver 通信, 实现状态报告、命令下发、启停容器等功能,相当于小管家。

kubelet 因为要管理整个节点,容器化会限制它的能力,所有运行在物理机上。

kube-proxy

kube-proxy 是 Node 的网络代理,转发 TCP/UDP 数据包,相当于专职小邮差

container-runtime

插件

addons

比较重要的两个插件:DNS、Dashboard

第十一讲 API

YAML

k8s 使用 yaml 声明式(declarative)地描述资源

yaml

API 对象

k8s 在理论层面抽象出了许多个概念,用来描述系统的管理运维工作,这些概念叫做 “API 对象”。

可以使用kubectl api-resources 查看对象列表

api_resources

如何描述 API 对象

命令式启动pod kubectl run ngx --image=nginx:alpine 可以改写成如下声明式配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
apiVersion: v1
kind: Pod
metadata:
  name: ngx-pod
  labels:
    env: demo
    owner: chrono

spec:
  containers:
    - image: nginx:alpine
      name: ngx
      ports:
        - containerPort: 80

可以将 配置类比成 HTTP 报文,分为两部分

  • header: apiVersion, kind, metadata
  • body: spec

可以使用如下命令创建和删除资源

1
2
3
kubectl apply -f ngx-pod.yaml

kubectl delete -f ngx-pod.yaml

编写 yaml 技巧

  1. 使用kubectl api-resources查看对应的 API 版本 和 类型
  2. 使用kubectl explain 查看对应资源的详细说明
  3. 使用--dry-run=client -o yaml 生成 yaml 文件

第十二讲 Pod

Pod 是对容器的"打包",是 k8s 最小的调度部署单位, 类似于 Linux 里面的进程组

pod

如何使用 yaml 描述 Pod

Pod 有四个基本组成部分

  • apiVersion: 固定为 v1
  • kind: 固定为 Pod
  • metadata:
    • name: 指定名称,可以带 pod 后缀方便和其他资源区分
    • labels: 标签,可添加任意的 key/value, 如 env=dev、region=north
  • containers: 容器列表
    • image: 镜像
    • name: 名字
    • ports:对外暴露的端口
    • imagePullPolicy: 镜像拉取策略,默认为 IfNotPresent, 本地不存在才会来取远程镜像
    • env: 定义环境变量,运行时指定
    • command: 容器启动时执行的命令,相当于Dockerfile 的 ENTRYPOINT
    • args: command 参数,相当于 Dockerfile 的 CMD 指令

如何使用 kubectl 操作 Pod

使用 kubectl apply -fkubectl delete -f 创建或删除 Pod

使用 kubectl logs 产看日志

使用 kubectl exec -it $pod -- sh 进入 Pod 内部

第十三讲 Job/CronJob

Job/CronJob 都属于离线业务,区别在于 Job 只运行一次

Job

Job yaml 相比 Pod, 区别在于 spec 使用 template 字段来定义 Pod

job

使用 kubectl apply -f job.yaml 启动 job

使用 kubectl get job 查看 job 状态 使用 kubectl get pod 查看 job 中的 pod 的 状态

job_status

部分离线作业重要字段

  • activeDeadlineSeconds: pod 超时时间
  • backoffLimit: pod 失败重试次数
  • completions: Job 需要完成运行多少个 Pod, 默认为1
  • parallelism: completions 对应的并发度

示例如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: batch/v1
kind: Job
metadata:
  name: sleep-job

spec:
  activeDeadlineSeconds: 15
  backoffLimit: 2
  completions: 4
  parallelism: 2

  template:
    spec:
      restartPolicy: OnFailure
      containers:
        - image: busybox
          name: echo-job
          imagePullPolicy: IfNotPresent
          command:
            - sh
            - -c
            - sleep $(($RANDOM % 10 + 1)) && echo done

CronJob

CronJob 其实组合了Job

cronjob

第十四讲 ConfigMap/Secret

ConfigMap 保存明文配置, Secret 保存 私密配置

ConfigMap

ConfigMap 可以使用 kubectl create cm info --from-literal=aa=bb --dry-run=client -o yaml > info-cm.ayml 创建模版

示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
apiVersion: v1
kind: ConfigMap
metadata:
  name: info

data:
  count: '10'
  debug: 'on'
  path: '/etc/systemd'
  greeting: |
        say hello to kubernetes.

创建和查看

1
2
3
4
5
kubectl apply -f info-cm.yaml

kubectl get cm

kubectl describe cm info

Secret

Secret 使用 kubectl create secret generic 创建模板

示例

1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Secret
metadata:
  name: user

data:
  name: cm9vdA==  # root
  pwd: MTIzNDU2   # 123456
  db: bXlzcWw=    # mysql
1
2
3
4
5
6
7
kubectl create secret generic user  --from-literal=aa=bb --dry-run=client -o yaml > user-secret.yaml

kubectl apply -f user-secret.yaml

kubectl get secret

kubectl describe secret user

使用

环境变量

使用 configMapKeyRefsecretKeyRef 来引用

 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
apiVersion: v1
kind: Pod
metadata:
  name: env-pod

spec:
  containers:
  - env:
      - name: COUNT
        valueFrom:
          configMapKeyRef:
            name: info
            key: count
      - name: GREETING
        valueFrom:
          configMapKeyRef:
            name: info
            key: greeting
      - name: USERNAME
        valueFrom:
          secretKeyRef:
            name: user
            key: name
      - name: PASSWORD
        valueFrom:
          secretKeyRef:
            name: user
            key: pwd

    image: busybox
    name: busy
    imagePullPolicy: IfNotPresent
    command: ["/bin/sleep", "300"]

引用示意图如下

config_map_ref

加载文件

Pod 可以挂载 Volume, 可以在 spec 中指定,在 containers引用

引用示意图如下

config_map_volume

示意配置如下

 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
apiVersion: v1
kind: Pod
metadata:
  name: vol-pod

spec:
  volumes:
  - name: cm-vol
    configMap:
      name: info
  - name: sec-vol
    secret:
      secretName: user

  containers:
  - volumeMounts:
    - mountPath: /tmp/cm-items
      name: cm-vol
    - mountPath: /tmp/sec-items
      name: sec-vol

    image: busybox
    name: busy
    imagePullPolicy: IfNotPresent
    command: ["/bin/sleep", "300"]

挂载后,ConfigMap 和 Secret 的 键成了文件名,值成了文件内容

config_map_volume2

第十七讲 搭建多节点 k8s 集群

实验环境准备

machine

安装前

  1. 修改hostname, k8s 使用 hostname 来区分集群节点,所以 hostname 不能重名。
1
sudo vim /etc/hostname 
  1. 安装 docker 运行时,将其 cgroup 的驱动程序改为 systemd
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
cat <<EOF | sudo tee /etc/docker/daemon.json
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2"
}
EOF

sudo systemctl enable docker
sudo systemctl daemon-reload
sudo systemctl restart docker
  1. 启用 br_netfilter 模块, 使 k8s 能够检查和转发网络流量
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
br_netfilter
EOF

cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward=1 # better than modify /etc/sysctl.conf
EOF

sudo sysctl --system
  1. 关闭 swap, 提升 k8s 性能
1
2
sudo swapoff -a
sudo sed -ri '/\sswap\s/s/^#?/#/' /etc/fstab

安装 kubeadm

  1. 更换 k8s 下载源
1
2
3
4
5
6
7
8
9
sudo apt install -y apt-transport-https ca-certificates curl

curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | sudo apt-key add -

cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
EOF

sudo apt update
  1. 下载 kubeadm
1
2
3
4
sudo apt install -y kubeadm=1.23.3-00 kubelet=1.23.3-00 kubectl=1.23.3-00

# 锁定版本,避免错误升级
sudo apt-mark hold kubeadm kubelet kubectl
  1. 下载镜像

由于 k8s 镜像在 google 镜像仓库 gcr.io,国内访问困难, 改用国内镜像,然后使用 docker tag 改名

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
repo=registry.aliyuncs.com/google_containers

for name in `kubeadm config images list --kubernetes-version v1.23.3`; do

    src_name=${name#k8s.gcr.io/}
    src_name=${src_name#coredns/}

    docker pull $repo/$src_name

    docker tag $repo/$src_name $name
    docker rmi $repo/$src_name
done

安装 master 节点

1
2
3
4
5
6
# --pod-network-cidr 设置集群使用网段
# --apiserver-advertise-address 设置apiserver 地址
sudo kubeadm init \
    --pod-network-cidr=10.10.0.0/16 \
    --apiserver-advertise-address=192.168.10.210 \
    --kubernetes-version=v1.23.3

安装后复制 kubectl 配置文件

1
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

加入集群

1
2
kubeadm join 192.168.10.210:6443 --token tv9mkx.tw7it9vphe158e74 \
  --discovery-token-ca-cert-hash sha256:e8721b8630d5b562e23c010c70559a6d3084f629abad6a2920e87855f8fb96f3

安装网络插件,这里使用 flannel

使用配置模版 kube-flannel.yml, 修改网段

1
2
3
4
5
6
7
net-conf.json: |
  {
    "Network": "10.10.0.0/16",
    "Backend": {
      "Type": "vxlan"
    }
  }  

应用

1
kubectl  apply -f kube-flannel.yml

使用 kubectl get node 验证

安装 worker 节点

克隆 master 虚拟机, 使用 kubeamd join 指令加入即可

第十八讲 Deployment

Deployment 主要用来管理在线业务 Pod

生成默认模版

1
kubectl create deploy ngx-dep --image=nginx:alpine --dry-run=client -o yaml > ngx-dep.yaml
 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:
  labels:
    app: ngx-dep
  name: ngx-dep
  
spec:
  replicas: 2
  selector:
    matchLabels:
      app: ngx-dep
      
  template:
    metadata:
      labels:
        app: ngx-dep
    spec:
      containers:
      - image: nginx:alpine
        name: nginx

replicas 表示副本数量

selector 作用是筛选出要被 Deployment 管理的 Pod 对象, 下属 matchLabels 必须和 templatelabels 完全相同

deployment_yaml

1
2
3
4
5
6
7
8
# 查看deploy状态
kubectl get deploy

# 使用标签筛选 pod
kubectl get pod -l app=ngx-dep

# 调整 replicas
kubectl scale --replicas=5  deploy ngx-dep

第十九讲 DaemonSet

介绍

DaemonSet 会在 满足条件的所有节点上运行一个 Pod, 就像 Linux 系统里的 守护进程(Daemon)

一些应用与节点存在绑定关系,如

  • 网络应用 kube-proxy
  • 监控应用 prometheus
  • 日志应用 fluentd

DaemonSet 不可以用 kubectl create 创建模板,可以在 Deployment 模版上修改 , 主要区别就是 DaemonSet 没有 replicas 字段

daemon_set_yaml

taint 和 toleration

节点的标签有一个专门字段taint 污点来表述,Pod 可以配置 toleratoin 来筛选 taint

taint 可以在 kubectl describe node 查看

1
2
3
4
5
# 添加 taint
kubectl taint node master node-role.kubernetes.io/master:NoSchedule-

# 结尾加"-" 来移除 taint
kubectl taint node master node-role.kubernetes.io/master:NoSchedule-

可以 Pod 的 spec 下 选择 taint

1
2
3
4
tolerations:
- key: node-role.kubernetes.io/master
  effect: NoSchedule
  operator: Exists

静态 Pod

静态 Pod 不受 k8s 系统管控, 不与 apiserver 发生关系

对应配置文件默认存放在 /etc/kubernetes/manifests 目录下

static_pods

如 master 节点的四个核心组件都是以 静态 Pod 形式存在的

第二十讲 Service

Service 工作原理和 Nginx 差不多, k8s 会分给它一个静态 IP, 然后它再去自动管理维护后面动态变化的 Pod 集合,当客户端访问 Service,它根据某种策略把流量转发给后面某个 Pod.

service

可以使用如下命令创建 yaml 模版

1
kubectl expose deploy ngx-dep --port=80 --target-port=80 --dry-run=client -o yaml

配置中主要有两个关键字 selectorports

service_yaml

查看 ngx-svc 详情

service_detail

exec 到 pod 进行测试

service_test

k8s 会自动给对象分配域名,可以使用 ngx-svc等域名访问

service_dns

service 默认为 ClusterIP 类型,即静态 IP 只能集群内部访问。 可以使用 NodePort 类型, 这样节点会提供一个对外暴露的端口指向该 service

1
2
3
4
5
6

apiVersion: v1
...
spec:
  ...
  type: NodePort

NodePort 类型有如下缺点:

  • 端口有限,默认只在 30000~32767 范围分配
  • 每个节点都开端口,提高了网络通信成本
  • 需要对外暴露节点的 IP 地址,为了安全需要搭建一个反向代码,增加了复杂度。

第二十二讲 实战演练

要点回顾

Service 是 Pod IP 地址的抽象,它拥有一个固定的 IP 地址,再使用 iptables 规则 把 流量负责均衡到后面的 Pod, 节点上的 kube-proxy 组件后实时维护被代理的 Pod 状态, 保证 Service 只会转发给健康的 Pod

Service 还基于 DNS 插件支持域名,所以客户端不在需要关心 Pod 具体情况

Service 是四层的负载均衡,而 Ingress 是 七层负责均衡

Ingress 需要 IngressController 和 IngressClass 配合工作

  • Controller 是真正的集群入口,应用 Ingress 规则调度、分发流量;此外还提供反向代理的角色
  • Class 是用来分组管理 Ingress

IngressController 本身也是一个 Pod, 需要通过 NodePort、LoadBalancer等方式暴露到集群外。

WordPress 网站搭建

wp_arc

1. 部署 MariaDB

 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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
---

apiVersion: v1
kind: ConfigMap
metadata:
  name: maria-cm

data:
  DATABASE: 'db'
  USER: 'wp'
  PASSWORD: '123'
  ROOT_PASSWORD: '123'
  
---

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: maria-dep
  name: maria-dep

spec:
  replicas: 1
  selector:
    matchLabels:
      app: maria-dep

  template:
    metadata:
      labels:
        app: maria-dep
    spec:
      containers:
        - image: mariadb:10
          name: mariadb
          ports:
            - containerPort: 3306

          envFrom:
            - prefix: 'MARIADB_'
              configMapRef:
                name: maria-cm
---

apiVersion: v1
kind: Service
metadata:
  labels:
    app: maria-dep
  name: maria-svc

spec:
  ports:
    - port: 3306
      protocol: TCP
      targetPort: 3306
  selector:
    app: maria-dep

使用 ConfigMap + Development + Service 的方式

1
kubectl appy -f wp-maria.yaml

2. 部署 WordPress

 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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
apiVersion: v1
kind: ConfigMap
metadata:
  name: wp-cm

data:
  HOST: 'maria-svc'
  USER: 'wp'
  PASSWORD: '123'
  NAME: 'db'

---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: wp-dep
  name: wp-dep

spec:
  replicas: 2
  selector:
    matchLabels:
      app: wp-dep

  template:
    metadata:
      labels:
        app: wp-dep
    spec:
      containers:
        - image: wordpress:5
          name: wordpress
          ports:
            - containerPort: 80

          envFrom:
            - prefix: 'WORDPRESS_DB_'
              configMapRef:
                name: wp-cm

---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: wp-dep
  name: wp-svc

spec:
  ports:
    - name: http80
      port: 80
      protocol: TCP
      targetPort: 80
      nodePort: 30088

  selector:
    app: wp-dep
  type: NodePort
1
2
3
4
同样使用 ConfigMap + Development + Service  的方式

```shell
kubectl appy -f wp-dep.yaml

wp-svc 使用 NodePort 暴露到集群外

3. 部署 Nginx Ingress Controller

定义 IngressClass

1
2
3
4
5
6
7
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: wp-ink

spec:
  controller: nginx.org/ingress-controller

定义 Ingress

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: wp-ing

spec:
  ingressClassName: wp-ink

  rules:
  - host: wp.test
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: wp-svc
            port:
              number: 80

定义 IngressController

 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
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/
kind: Deployment
metadata:
  name: wp-kic-dep
  namespace: nginx-ingress

spec:
  replicas: 1
  selector:
    matchLabels:
      app: wp-kic-dep

  template:
    metadata:
      labels:
        app: wp-kic-dep

    spec:
      serviceAccountName: nginx-ingress
      hostNetwork: true

      dnsPolicy: ClusterFirstWithHostNet

      containers:
      #- image: nginx/nginx-ingress:2.2.0
      - image: nginx/nginx-ingress:2.2-alpine
        imagePullPolicy: IfNotPresent
        name: nginx-ingress
        ports:
        - name: http
          containerPort: 80
        - name: https
          containerPort: 443
        - name: readiness-port
          containerPort: 8081
        - name: prometheus
          containerPort: 9113
        readinessProbe:
          httpGet:
            path: /nginx-ready
            port: readiness-port
          periodSeconds: 1
        securityContext:
          allowPrivilegeEscalation: true
          runAsUser: 101 #nginx
          capabilities:
            drop:
            - ALL
            add:
            - NET_BIND_SERVICE
        env:
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        args:
          - -ingress-class=wp-ink
          - -health-status
          - -ready-status
          - -nginx-status
          - -enable-snippets

          - -nginx-configmaps=$(POD_NAMESPACE)/nginx-config
          - -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret
         #- -v=3 # Enables extensive logging. Useful for troubleshooting.
         #- -report-ingress-status
         #- -external-service=nginx-ingress
         #- -enable-prometheus-metrics
         #- -global-configuration=$(POD_NAMESPACE)/nginx-configuration

---

apiVersion: v1
kind: Service
metadata:
  name: wp-kic-svc
  namespace: nginx-ingress

spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
    nodePort: 30080

  selector:
    app: wp-kic-dep
  type: NodePort

需要执行 ingress 目录 setup.sh 脚本

1
2
# https://github.com/chronolaw/k8s_study/blob/master/ingress/setup.sh
./setup.sh

在 /etc/hosts 加上 worker node 对 wp.test 映射后即可使用 http://wp.test 访问服务

第二十四讲 PersistentVolume

PersistentVolume, 简称 PV 是存储的抽象,实际就是一些文件系统,如 GlusterFS、NFS、本地磁盘

PersistentVolumeClaim, 简称 PVC,用来代表 Pod 向系统申请 PV,申请成功后会将 PV 和 PVC 绑定在一起

StorageClass 抽象了特定的存储类型,在 PVC 和 PV 之间充当协调人的角色。

storage_class

PV 配置文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14

apiVersion: v1
kind: PersistentVolume
metadata:
  name: host-10m-pv

spec:
  storageClassName: host-test
  accessModes:
    - ReadWriteOnce
  capacity:
    storage: 10Mi
  hostPath:
    path: /tmp/host-10m-pv/

accessModes 定义了设备访问模式:

  • ReadWriteOnce: 可读写,但只能被一个 Node 上的 Pod 挂载
  • ReadOnlyMany: 仅可读,可以被多个 Node 上的 Pod 挂载
  • ReadWriteMany: 可读写,可以被多个 Node 上的 Pod 挂载

PVC 配置文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: host-5m-pvc

spec:
  storageClassName: host-test
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Mi

Pod 示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
apiVersion: v1
kind: Pod
metadata:
  name: host-pvc-pod

spec:
  volumes:
  - name: host-pvc-vol
    persistentVolumeClaim:
      claimName: host-5m-pvc

  containers:
    - name: ngx-pvc-pod
      image: nginx:alpine
      ports:
      - containerPort: 80
      volumeMounts:
      - name: host-pvc-vol
        mountPath: /tmp

p2

第二十五讲 PV+NFS

安装 NFS

服务端

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
sudo apt -y install nfs-kernel-server


mkdir -p /tmp/nfs

# 添加配置
sudo echo '/tmp/nfs 192.168.10.0/24(rw,sync,no_subtree_check,no_root_squash,insecure)'  >> /etc/exports
# 通知 nfs 
sudo exportfs -ra
# 查看详情
sudo exportfs -v

# 检查网络挂载
showmount -e localhost

客户端

1
2
3
4
5
6
7
8
sudo apt -y install nfs-common

# 测试服务端挂载情况
showmount -e 192.168.10.208


mkdir -p /tmp/test
sudo mount -t nfs 192.168.10.208:/tmp/nfs /tmp/test

静态 PV

pv 配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-1g-pv

spec:
  storageClassName: nfs
  accessModes:
    - ReadWriteMany
  capacity:
    storage: 1Gi

  nfs:
    path: /tmp/nfs/1g-pv
    server: 192.168.10.208

pvc 配置

 1
 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: 1Gi

测试 pod 配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: v1
kind: Pod
metadata:
  name: nfs-static-pod

spec:
  volumes:
  - name: nfs-pvc-vol
    persistentVolumeClaim:
      claimName: nfs-static-pvc

  containers:
    - name: nfs-pvc-test
      image: nginx:alpine
      ports:
      - containerPort: 80

      volumeMounts:
        - name: nfs-pvc-vol
          mountPath: /tmp

示意图

p1

动态 PV

使用 Provisioner 来自动管理存储、创建 PV ,

NFS Provisioner 是以 Pod 形式运行在 k8s,需要三个部署文件 rabc.yaml、class.yaml 和 deployment.yaml

storageClass 配置

1
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: "false"

pvc 配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-dyn-10m-pvc

spec:
  storageClassName: nfs-client
  accessModes:
    - ReadWriteMany

  resources:
    requests:
      storage: 10Mi

测试 pod 配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: v1
kind: Pod
metadata:
  name: nfs-dyn-pod

spec:
  volumes:
    - name: nfs-dyn-10m-vol
      persistentVolumeClaim:
        claimName: nfs-dyn-10m-pvc

  containers:
    - name: nfs-dyn-test
      image: nginx:alpine
      ports:
        - containerPort: 80

      volumeMounts:
        - name: nfs-dyn-10m-vol
          mountPath: /tmp

由于有 NFS Provisioner, 不再需要直接定义 PV 对象,会自动按 PVC 配置创建。

整体示意图

p2

第二十六讲 StatefulSet

StatefulSets 和 Deployment 类似, 不过 pod 名称是固定的,pod 启动是有序的。

 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: StatefulSet
metadata:
  name: redis-sts

spec:
  serviceName: redis-svc
  replicas: 2
  selector:
    matchLabels:
      app: redis-sts

  template:
    metadata:
      labels:
        app: redis-sts
    spec:
      containers:
      - image: redis:5-alpine
        name: redis
        ports:
        - containerPort: 6379

声明 svc

 1
 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

验证网络

p1

svc 和 sts 之间的关联

p2

sts 可以通过定义 volumeClaimTemplates 来声明 pvc

 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
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis-pv-sts

spec:
  serviceName: redis-pv-svc

  volumeClaimTemplates:
  - metadata:
      name: redis-100m-pvc
    spec:
      storageClassName: nfs-client
      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

三者关联图

p3

pvc 状态

p4

第二十七讲 滚动更新

更新

k8s 里面的应用的版本变化就是指 template 里的 Pod 变化,最终 使用 template 的 Hash 值作为 版本号

apply 更新的 yaml 后,可以使用kubectl rollout status查看更新状态

p1

可以使用 kubectl describe deployment ngx-dep 查看具体的 pod 变化

p2

pod 变化过程示意图

p3

历史及回滚

可使用 kubectl rollout history deployment ngx-dep 查看更新历史, 默认保存最近十次记录

可使用 kubectl rollout history deployment ngx-dep --revision=2 查看具体历史的详情

p4

可使用 kubectl rollout undo deployment ngx-dep 来回滚

可使用 kubectl rollout undo deployment ngx-dep --to-revision=3 来回滚到任意版本

回滚时 pod 变化过程示意图

p5

添加版本备注信息

1
2
3
4
5
6
7
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ngx-dep
  annotations:
    kubernetes.io/change-cause: v1, ngx=1.21
... ...

第二十八讲 如何监控 Pod 健康状态

资源配额

通过在 容器描述部分添加 resources 字段来声明资源

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: Pod
metadata:
  name: ngx-pod-resources

spec:
  containers:
  - image: nginx:alpine
    name: ngx

    resources:
      requests:
        cpu: 10m
        memory: 100Mi
      limits:
        cpu: 20m
        memory: 200Mi

requests 表示容器需要申请的资源,limits 表示资源使用上限,超过则可能被强制停止。

cpu 的单位m代表千分之一。

如果不加资源声明,k8s 可以将 pod 调度到任意节点上,否则 k8s 会根据每个 Pod 声明的需求,像搭积木或者玩俄罗斯方块一样,把节点尽量塞满。

检查探针

k8s 定义了三种探针

  • Startup: 启动探针,用来检查应用是否启动成功
  • Liveness: 存活探针,用来检查应用是否正常运行
  • Readiness: 就绪探针,用来检查应用是否可以接受流量

三种探针是递进关系,Startup 成功后,才会执行后面两个探针

p1

如果 Liveness 探针失败, k8s 会重启容器,Readiness 探针失败,会将容器从 Service 负载均衡集合中移除,不分配流量。

p2

k8s 支持使用 shell、tcp、http 三种方式检测

 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
apiVersion: v1
kind: Pod
metadata:
  name: ngx-pod-probe

spec:
  volumes:
  - name: ngx-conf-vol
    configMap:
      name: ngx-conf

  containers:
  - image: nginx:alpine
    name: ngx
    ports:
    - containerPort: 80
    volumeMounts:
    - mountPath: /etc/nginx/conf.d
      name: ngx-conf-vol

    startupProbe:
      periodSeconds: 1
      exec:
        command: ["cat", "/var/run/nginx.pid"]

    livenessProbe:
      periodSeconds: 10
      tcpSocket:
        port: 80

    readinessProbe:
      periodSeconds: 5
      httpGet:
        path: /ready
        port: 80

第二十九讲 命名空间

命名空间是一个逻辑上的概念,它可以将集群切分成一个个彼此独立的区域

创建命名空间

1
kubectl create ns dev-ns

有了命名空间,就可以给命名空间设定资源配额,示例如下

 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

apiVersion: v1
kind: ResourceQuota
metadata:
  name: dev-qt
  namespace: dev-ns

spec:
  hard:
    requests.cpu: 10
    requests.memory: 10Gi
    limits.cpu: 10
    limits.memory: 20Gi

    requests.storage: 100Gi
    persistentvolumeclaims: 100

    pods: 100
    configmaps: 100
    secrets: 100
    services: 10

    count/jobs.batch: 1
    count/cronjobs.batch: 1
    count/deployments.apps: 1

设定了资源配额之后,所有 Pod 都需要显式声明资源需求,但这样 的严格要求有时不太方便,我们可以声明一个默认资源配额

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
apiVersion: v1
kind: LimitRange
metadata:
  name: dev-limits
  namespace: dev-ns

spec:
  limits:
  - type: Container
    defaultRequest:
      cpu: 200m
      memory: 50Mi
    default:
      cpu: 500m
      memory: 100Mi
  - type: Pod
    max:
      cpu: 800m
      memory: 200Mi

第三十讲 如何使用 Metrics Server 和 Prometheus

Metrics Server

metrics server 是一个专门来收集核心资源指标的工具,定时从所有节点的 kubelet 里采集信息。

p1

安装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#  下载配置yaml
wget https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml


# 修改配置 
# deployment.spec.template.spec.containers 对象 添加 '- --kubelet-insecure-tls'
# deployment.spec.template.spec 对象 添加 'nodeName: k8s-master #你自己的节点名称'

# 重命名镜像

repo=registry.aliyuncs.com/google_containers

name=k8s.gcr.io/metrics-server/metrics-server:v0.6.1
src_name=metrics-server:v0.6.1

docker pull $repo/$src_name

docker tag $repo/$src_name $name
docker rmi $repo/$src_name


# 应用

kubectl apply -f components.yaml

查看 p2

HorizontalPodAutoscaler

HorizontalPodAutoscaler 是 用来 自动伸缩 Pod 数量的对象,简称 hpa.

hpa 从 Metrics Server 获取当前应用的指标,按照策略增减 Pod 数量

使用 nginx deploy 示例

 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
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ngx-hpa-dep

spec:
  replicas: 1
  selector:
    matchLabels:
      app: ngx-hpa-dep

  template:
    metadata:
      labels:
        app: ngx-hpa-dep
    spec:
      containers:
        - image: nginx:alpine
          name: nginx
          ports:
            - containerPort: 80

          resources:
            requests:
              cpu: 50m
              memory: 10Mi
            limits:
              cpu: 100m
              memory: 20Mi
---

apiVersion: v1
kind: Service
metadata:
  name: ngx-hpa-svc
spec:
  ports:
    - port: 80
      protocol: TCP
      targetPort: 80
  selector:
    app: ngx-hpa-dep

创建 hpa 模版

1
2
export out="--dry-run=client -o yaml"              # 定义Shell变量
kubectl autoscale deploy ngx-hpa-dep --min=2 --max=10 --cpu-percent=5 $out
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: ngx-hpa

spec:
  maxReplicas: 10
  minReplicas: 2
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: ngx-hpa-dep
  targetCPUUtilizationPercentage: 5

Prometheus

使用 kube-prometheus 部署比较方便

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#  下载部署文件
wget https://github.com/prometheus-operator/kube-prometheus/archive/refs/tags/v0.11.0.tar.gz


# 修改配置
##  替换gcr 镜像 kubeStateMetrics-deployment.yaml、prometheusAdapter-deployment.yaml
image: k8s.gcr.io/kube-state-metrics/kube-state-metrics:v2.5.0
image: k8s.gcr.io/prometheus-adapter/prometheus-adapter:v0.9.1

image: chronolaw/kube-state-metrics:v2.5.0
image: willdockerhub/prometheus-adapter:v0.9.1
## 添加NodePort IP ,方便本机测试 prometheus-service.yaml、grafana-service.yaml
spec.type: NodePort

# 创建服务
kubectl create -f manifests/setup
kubectl create -f manifests

使用 kubectl get svc -n monitoring 获取 prometheus-k8s 服务 9090对应的端口、grafana 3000 对应的端口, 浏览器访问验证

第三十一讲 网络通信

CNI

为了适应跨主机通信的需求,k8s 提出了自己的网络模型 IP-per-pod:

  • 每个 pod 有唯一的 IP 地址
  • pod 里所有容器共享这个 IP 地址
  • 集群内所有 pod 共享一个网段
  • pod 可以直接基于 IP 访问另外一个 pod

k8s 定义了一个实现上述网络模型的标准 CNI(container networking interface), 根据实现原理, CNI 插件大致分为三类:

  • Overlay: 构建了一个工作在真实底层网络之上的"逻辑网络",把原始的 Pod 网络数据封包,再通过下层网络发送出去,到了目的地再拆包。特点是适应性强,但性能较低。
  • Route:使用系统内置的路由功能来实现 Pod 跨主机通信。特点是性能较高,但对底层网络依赖性强。
  • Underlay:直接使用底层网络来实现CNI,Pod 和主机在一个网络里。 特点是性能最高,但对底层硬件和网络依赖性最强。

插件介绍

  • Flannel:早期使用 UDP 和 VXLAN 实现了 Overlay 模式,后来使用 Host-Gateway 支持了 Route 模式
  • Calico:Route 模式,使用 BGP 协议来维护路由信息,支持多种网络策略,具备数据加密、安全隔离、流量整形等功能
  • Cilium:同时支持 Overlay 和 Route 模式,深度使用了 eBPF 技术,在内核层次操作网络数据,性能很高。

Flannel 工作方式分析

创建 3 个 nginx pod

1
kubectl create deploy ngx-dep --image=nginx:alpine --replicas=3

p1

默认使用基于 VXLAN 的 Overlay 模式,从单机的角度来看,Flannel 的网络结构几乎和 Docker 一模一样,只不过网桥从 docker0 换成 了 cni0

p2

本机网络连接示意图如下

p3

route 查看路由表

  • 10.10.0.0/24 网段的数据,都要走 cni0 设备,也就是“cni0”网桥。
  • 10.10.1.0/24 网段的数据,都要走 flannel.1 设备,也就是 Flannel。
  • 192.168.10.0/24 网段的数据,都要走 ens160 设备,也就是我们宿主机的网卡

假设我们要从 master 节点的“10.10.0.3”访问 worker 节点的“10.10.1.77”,因为 master 节点的“cni0”网桥管理的只是“10.10.0.0/24”这个网段, 所以按照路由表,凡是“10.10.1.0/24”都要让 flannel.1 来处理, 这样就进入了 Flannel 插件的工作流程。

p4

跨主机网络示意图如下

p5

链接