Kubernetes

Stone大约 74 分钟

Kubernetes

概述

Kubernetesopen in new window 是一个管理容器和服务的开源平台,用于在多主机上高效部署,升级,回滚,扩缩容及管理容器应用,以实现容器应用和服务的负载均衡及故障转移。

相比于传统单主机部署应用及虚拟化多主机部署应用,使用容器化部署应用既可以实现资源隔离,还可以更高效的使用资源。类似于使用 VMware vSphere 管理多个主机上的虚拟机,使用 Kubernetes 管理多个主机上的容器,以实现一个可弹性运行的分布式系统,以满足容器应用的高可用,高性能和高扩展。

container_evolution

架构

Kubernetes 集群使用客户端-服务器架构。

components-of-kubernetes

其中:

  • Control Plane:控制平面,管理集群,包括:
    • kube-apiserver:控制平面的前端接口,暴露 Kubernetes API。
    • kube-controller-manager:控制器管理器,运行控制器进程,包括:
      • Node controller
      • Job controller
      • EndpointSlice controller
      • ServiceAccount controller
    • cloud-controller-manager:用于云服务的控制器管理器,本地部署则没有该组件。
    • kube-scheduler:调度器,根据资源及配置来调度,决定将容器部署到哪个工作节点。
    • etcd:存储集群数据。
  • Node:工作节点,包括:
    • kubelet:运行在每个工作节点上的代理,拉取镜像,运行容器。
    • kube-proxy:运行在每个工作节点上的网络代理,维护节点上的网络规则。

除了以上 Kubernetes 自带的组件外,为便于系统的扩展,还开放了以下接口:

image-20230904134235853

image-20230821203029583

  • CNI(Container Network Interface):容器网络接口,为容器提供网络服务,常用的实现有 Flannelopen in new windowCalicoopen in new window 等。
  • CSI(Container Storage Interface):容器存储接口,为容器提供存储服务。

部署

本节介绍使用 kubeadm 在 CentOS 上部署 Kubernetes 集群。

环境

主机规划:

No.HostIPCPUMemoryRole
1k8sm1192.168.92.14024 GBControl Plane
2k8sn1192.168.92.14124 GBWorker Node 1
3k8sn2192.168.92.14224 GBWorker Node 2

注意:

CPU 数量至少为 2,内存建议至少 4 GB。

软件版本:

No.NameRelease
1CentOS7.9
2Docker20.10.23
3Containerd1.6.15
4kubernates1.23.17
5Flannel0.22.2
6Ingress-Nginx1.6.4
7Dashboard2.5.1

注意:

Docker 和 Containerd 的版本对应关系可在 Dockeropen in new window 查看。

Kubernetes 和 Docker 的版本对应关系可在 Githubopen in new window 查看。

Kubernetes 和 Flannel 的版本对应关系可在 Githubopen in new window 查看。

Kubernetes 和 Ingress-Nginx 的版本对应关系可在 Githubopen in new window 查看。

Kubernetes 和 Dashboard 的版本对应关系可在 Githubopen in new window 查看。

准备

安装 CentOSopen in new window 操作系统,分别配置 3 台主机的主机名open in new window网络地址open in new window

Control Plane:

[root@localhost ~]# hostnamectl set-hostname k8sm1.stonecoding.net
[root@localhost ~]# vi /etc/sysconfig/network-scripts/ifcfg-ens33 
TYPE="Ethernet"
BOOTPROTO="none"
DEFROUTE="yes"
IPADDR=192.168.92.140
NETMASK=255.255.255.0
GATEWAY=192.168.92.2
NAME="ens33"
DEVICE="ens33"
ONBOOT="yes"
UUID="9b6cf18b-7e51-4d1f-818e-142bb03ee6eb"
[root@localhost ~]# systemctl restart network 

Node 1:

[root@localhost ~]# hostnamectl set-hostname k8sn1.stonecoding.net
[root@localhost ~]# vi /etc/sysconfig/network-scripts/ifcfg-ens33 
TYPE="Ethernet"
BOOTPROTO="none"
DEFROUTE="yes"
IPADDR=192.168.92.141
NETMASK=255.255.255.0
GATEWAY=192.168.92.2
NAME="ens33"
DEVICE="ens33"
ONBOOT="yes"
UUID="4e6bae92-d721-4188-ab0d-71b1b5d8711e"
[root@localhost ~]# systemctl restart network

Node 2:

[root@localhost ~]# hostnamectl set-hostname k8sn2.stonecoding.net
[root@localhost ~]# vi /etc/sysconfig/network-scripts/ifcfg-ens33 
TYPE="Ethernet"
BOOTPROTO="none"
DEFROUTE="yes"
IPADDR=192.168.92.142
NETMASK=255.255.255.0
GATEWAY=192.168.92.2
NAME="ens33"
DEVICE="ens33"
ONBOOT="yes"
UUID="3ac41122-f782-4a69-bc93-9df7846db136"
[root@localhost ~]# systemctl restart network

然后创建脚本 k8s-pre.sh

#!/bin/bash

# 配置 /etc/hosts
echo "=========edit /etc/hosts============="
cat >> /etc/hosts << EOF
192.168.92.140   k8sm1   k8sm1.stonecoding.net
192.168.92.141   k8sn1   k8sn1.stonecoding.net
192.168.92.142   k8sn2   k8sn2.stonecoding.net
EOF

# 配置 DNS
echo "=========edit /etc/resolv.conf ============="
cat >> /etc/resolv.conf << EOF
nameserver 192.168.92.2
EOF

# 安装基础软件
echo "=========install basic sofeware============="
yum -y install wget

# 关闭防火墙
echo "=========stop firewalld============="
systemctl stop firewalld
systemctl disable firewalld
systemctl status firewalld

# 关闭 NetworkManager
echo "=========stop NetworkManager ============="
systemctl stop NetworkManager
systemctl disable NetworkManager
systemctl status NetworkManager

# 关闭 SELinux
echo "=========disable selinux============="
sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
setenforce 0
getenforce

# 关闭 SWAP
echo "=========close swap============="
sed -ri 's/.*swap.*/#&/' /etc/fstab
swapoff -a
free -m

# 时间同步
echo "=========sync time============="
yum -y install chrony
cat >> /etc/chrony.conf << EOF
server ntp.aliyun.com iburst
EOF
systemctl start chronyd
systemctl enable chronyd
chronyc sources

# 将桥接的 IPv4 流量传递到 iptables 的链
echo "=========Letting iptables see bridged traffic============="
cat > /etc/modules-load.d/k8s.conf << EOF
overlay
br_netfilter
EOF
modprobe overlay
modprobe br_netfilter

cat > /etc/sysctl.d/k8s.conf << EOF
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system

# 配置 YUM
echo "=========config yum============="
cat > /etc/yum.repos.d/nginx.repo << EOF
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/rhel/7/x86_64/
gpgcheck=0
enabled=1
EOF
wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
# 如果是 RedHat,则需要执行下面替换命令
#sed -i "s/\$releasever/7/g" /etc/yum.repos.d/CentOS-Base.repo
wget -O /etc/yum.repos.d/epel.repo  http://mirrors.aliyun.com/repo/epel-7.repo
yum clean all
yum makecache
yum -y install net-tools telnet tree nmap sysstat lrzsz dos2unix bind-utils ipvsadm bash-completion nfs-utils ftp nginx git

# 安装 Docker
echo "=========install docker============="
wget http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repo
# 如果是 RedHat,则需要执行下面替换命令
#sed -i "s/\$releasever/7/g" /etc/yum.repos.d/docker-ce.repo
sed -i "s/https/http/g" /etc/yum.repos.d/docker-ce.repo
yum -y install docker-ce-20.10.23 docker-ce-cli-20.10.23 containerd.io-1.6.15
systemctl enable docker
systemctl start docker
docker version

cat > /etc/docker/daemon.json << EOF
{
"insecure-registries": ["harbor.stonecoding.net"],
"live-restore": true,
"registry-mirrors": ["https://dockerproxy.com","https://registry.cn-hangzhou.aliyuncs.com"],
"exec-opts": ["native.cgroupdriver=systemd"]
}
EOF

systemctl restart docker

# 安装 kubelet kubeadm kubectl
echo "=========install kubeadm============="
cat > /etc/yum.repos.d/kubernetes.repo << EOF
[kubernetes]
name=Kubernetes
baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF

yum -y install kubelet-1.23.17 kubeadm-1.23.17 kubectl-1.23.17
systemctl enable kubelet

分别在 3 台主机上运行:

[root@k8sm1 ~]# sh k8s-pre.sh
[root@k8sn1 ~]# sh k8s-pre.sh
[root@k8sn2 ~]# sh k8s-pre.sh

控制平面

在主机 k8sm1 上初始化控制平面:

[root@k8sm1 ~]# kubeadm init \
--control-plane-endpoint=192.168.92.140 \
--apiserver-advertise-address=192.168.92.140 \
--service-cidr=10.96.0.0/12 \
--pod-network-cidr=10.244.0.0/16 \
--kubernetes-version v1.23.17 \
--image-repository=registry.aliyuncs.com/google_containers

[init] Using Kubernetes version: v1.23.17
[preflight] Running pre-flight checks
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [k8sm1.stonecoding.net kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 192.168.92.140]
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [k8sm1.stonecoding.net localhost] and IPs [192.168.92.140 127.0.0.1 ::1]
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [k8sm1.stonecoding.net localhost] and IPs [192.168.92.140 127.0.0.1 ::1]
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Starting the kubelet
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[apiclient] All control plane components are healthy after 5.509988 seconds
[upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config-1.23" in namespace kube-system with the configuration for the kubelets in the cluster
NOTE: The "kubelet-config-1.23" naming of the kubelet ConfigMap is deprecated. Once the UnversionedKubeletConfigMap feature gate graduates to Beta the default name will become just "kubelet-config". Kubeadm upgrade will handle this transition transparently.
[upload-certs] Skipping phase. Please see --upload-certs
[mark-control-plane] Marking the node k8sm1.stonecoding.net as control-plane by adding the labels: [node-role.kubernetes.io/master(deprecated) node-role.kubernetes.io/control-plane node.kubernetes.io/exclude-from-external-load-balancers]
[mark-control-plane] Marking the node k8sm1.stonecoding.net as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule]
[bootstrap-token] Using token: xo6ca1.0wa790qn33gx9mwb
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to get nodes
[bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstrap-token] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstrap-token] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstrap-token] Creating the "cluster-info" ConfigMap in the "kube-public" namespace
[kubelet-finalize] Updating "/etc/kubernetes/kubelet.conf" to point to a rotatable kubelet client certificate and key
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

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 "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

You can now join any number of control-plane nodes by copying certificate authorities
and service account keys on each node and then running the following as root:

  kubeadm join 192.168.92.140:6443 --token xo6ca1.0wa790qn33gx9mwb \
        --discovery-token-ca-cert-hash sha256:63b42c8fda1a3a954c66de806fdd7ba32e6986b433e78f565ede301cd1b9e932 \
        --control-plane 

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.92.140:6443 --token xo6ca1.0wa790qn33gx9mwb \
        --discovery-token-ca-cert-hash sha256:63b42c8fda1a3a954c66de806fdd7ba32e6986b433e78f565ede301cd1b9e932 

其中:

  • --control-plane-endpoint:指定控制平面接入点,如果控制平面为多节点组成的高可用集群,则该接入点应该为集群的负载均衡地址。
  • --apiserver-advertise-address:指定 apiserver 的地址。
  • --service-cidr:指定服务网络地址段,默认为 10.96.0.0/12。
  • --pod-network-cidr:指定 Pod 网络地址段,不能与主机网络段相同。
  • --kubernetes-version:指定 Kubernetes 版本。
  • --image-repository:指定镜像仓库,需要指定国内的地址。

根据网络情况,有可能需要较多时间下载镜像,最终下载的镜像如下:

[root@k8sm1 ~]# docker images
REPOSITORY                                                        TAG        IMAGE ID       CREATED         SIZE
registry.aliyuncs.com/google_containers/kube-apiserver            v1.23.17   62bc5d8258d6   5 months ago    130MB
registry.aliyuncs.com/google_containers/kube-controller-manager   v1.23.17   1dab4fc7b6e0   5 months ago    120MB
registry.aliyuncs.com/google_containers/kube-scheduler            v1.23.17   bc6794cb54ac   5 months ago    51.9MB
registry.aliyuncs.com/google_containers/kube-proxy                v1.23.17   f21c8d21558c   5 months ago    111MB
registry.aliyuncs.com/google_containers/etcd                      3.5.6-0    fce326961ae2   8 months ago    299MB
registry.aliyuncs.com/google_containers/coredns                   v1.8.6     a4ca41631cc7   22 months ago   46.8MB
registry.aliyuncs.com/google_containers/pause                     3.6        6270bb605e12   24 months ago   683kB

根据输出日志,执行以下命令配置 kubectl

[root@k8sm1 ~]# mkdir -p $HOME/.kube
[root@k8sm1 ~]# sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
[root@k8sm1 ~]# sudo chown $(id -u):$(id -g) $HOME/.kube/config

完成后,就可以使用 kubectl 命令查看资源情况:

[root@k8sm1 ~]# kubectl get nodes
NAME              STATUS     ROLES                  AGE   VERSION
k8sm1.stonecoding.net   NotReady   control-plane,master   64m   v1.23.17
[root@k8sm1 ~]# kubectl get componentstatus
Warning: v1 ComponentStatus is deprecated in v1.19+
NAME                 STATUS    MESSAGE                         ERROR
controller-manager   Healthy   ok                              
scheduler            Healthy   ok                              
etcd-0               Healthy   {"health":"true","reason":""} 
[root@k8sm1 ~]# kubectl get service
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   64m
[root@k8sm1 ~]# kubectl get service -n kube-system
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
kube-dns   ClusterIP   10.96.0.10   <none>        53/UDP,53/TCP,9153/TCP   64m
[root@k8sm1 ~]# kubectl get pods -n kube-system
NAME                                      READY   STATUS    RESTARTS   AGE
coredns-6d8c4cb4d-qqk4w                   0/1     Pending   0          65m
coredns-6d8c4cb4d-vz5fs                   0/1     Pending   0          65m
etcd-k8sm1.stonecoding.net                      1/1     Running   0          65m
kube-apiserver-k8sm1.stonecoding.net            1/1     Running   0          65m
kube-controller-manager-k8sm1.stonecoding.net   1/1     Running   0          65m
kube-proxy-8k87h                          1/1     Running   0          65m
kube-scheduler-k8sm1.stonecoding.net            1/1     Running   0          65m

由于还没有安装网络插件,coredns 处于 Pending 状态,安装网络插件后会创建完成。

网络插件

根据输出日志,需要安装网络插件。常用的网络插件有 Flannelopen in new windowCalicoopen in new window,这里使用 Flannelopen in new window

下载 Flannel 资源配置清单文件(适用于 Kubernetes 1.17 及以后版本):

[root@k8sm1 ~]# wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

如果直接下载失败,可以尝试使用代理下载:

[root@k8sm1 ~]# wget https://ghproxy.com/https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

然后根据初始化控制平面时指定的 Pod 网络配置进行修改,最终的资源配置清单文件如下:

---
kind: Namespace
apiVersion: v1
metadata:
  name: kube-flannel
  labels:
    k8s-app: flannel
    pod-security.kubernetes.io/enforce: privileged
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  labels:
    k8s-app: flannel
  name: flannel
rules:
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - get
- apiGroups:
  - ""
  resources:
  - nodes
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - nodes/status
  verbs:
  - patch
- apiGroups:
  - networking.k8s.io
  resources:
  - clustercidrs
  verbs:
  - list
  - watch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  labels:
    k8s-app: flannel
  name: flannel
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: flannel
subjects:
- kind: ServiceAccount
  name: flannel
  namespace: kube-flannel
---
apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    k8s-app: flannel
  name: flannel
  namespace: kube-flannel
---
kind: ConfigMap
apiVersion: v1
metadata:
  name: kube-flannel-cfg
  namespace: kube-flannel
  labels:
    tier: node
    k8s-app: flannel
    app: flannel
data:
  cni-conf.json: |
    {
      "name": "cbr0",
      "cniVersion": "0.3.1",
      "plugins": [
        {
          "type": "flannel",
          "delegate": {
            "hairpinMode": true,
            "isDefaultGateway": true
          }
        },
        {
          "type": "portmap",
          "capabilities": {
            "portMappings": true
          }
        }
      ]
    }
  net-conf.json: |
    {
      "Network": "10.244.0.0/16",
      "Backend": {
        "Type": "vxlan"
      }
    }
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: kube-flannel-ds
  namespace: kube-flannel
  labels:
    tier: node
    app: flannel
    k8s-app: flannel
spec:
  selector:
    matchLabels:
      app: flannel
  template:
    metadata:
      labels:
        tier: node
        app: flannel
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: kubernetes.io/os
                operator: In
                values:
                - linux
      hostNetwork: true
      priorityClassName: system-node-critical
      tolerations:
      - operator: Exists
        effect: NoSchedule
      serviceAccountName: flannel
      initContainers:
      - name: install-cni-plugin
        image: docker.io/flannel/flannel-cni-plugin:v1.2.0
        command:
        - cp
        args:
        - -f
        - /flannel
        - /opt/cni/bin/flannel
        volumeMounts:
        - name: cni-plugin
          mountPath: /opt/cni/bin
      - name: install-cni
        image: docker.io/flannel/flannel:v0.22.2
        command:
        - cp
        args:
        - -f
        - /etc/kube-flannel/cni-conf.json
        - /etc/cni/net.d/10-flannel.conflist
        volumeMounts:
        - name: cni
          mountPath: /etc/cni/net.d
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      containers:
      - name: kube-flannel
        image: docker.io/flannel/flannel:v0.22.2
        command:
        - /opt/bin/flanneld
        args:
        - --ip-masq
        - --kube-subnet-mgr
        resources:
          requests:
            cpu: "100m"
            memory: "50Mi"
        securityContext:
          privileged: false
          capabilities:
            add: ["NET_ADMIN", "NET_RAW"]
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: EVENT_QUEUE_DEPTH
          value: "5000"
        volumeMounts:
        - name: run
          mountPath: /run/flannel
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
        - name: xtables-lock
          mountPath: /run/xtables.lock
      volumes:
      - name: run
        hostPath:
          path: /run/flannel
      - name: cni-plugin
        hostPath:
          path: /opt/cni/bin
      - name: cni
        hostPath:
          path: /etc/cni/net.d
      - name: flannel-cfg
        configMap:
          name: kube-flannel-cfg
      - name: xtables-lock
        hostPath:
          path: /run/xtables.lock
          type: FileOrCreate

部署:

[root@k8sm1 ~]# kubectl apply -f kube-flannel.yml 
namespace/kube-flannel created
clusterrole.rbac.authorization.k8s.io/flannel created
clusterrolebinding.rbac.authorization.k8s.io/flannel created
serviceaccount/flannel created
configmap/kube-flannel-cfg created
daemonset.apps/kube-flannel-ds created

[root@k8sm1 ~]# docker images
REPOSITORY                                                        TAG        IMAGE ID       CREATED         SIZE
flannel/flannel                                                   v0.22.2    d73868a08083   47 hours ago    70.2MB
flannel/flannel-cni-plugin                                        v1.2.0     a55d1bad692b   4 weeks ago     8.04MB
registry.aliyuncs.com/google_containers/kube-apiserver            v1.23.17   62bc5d8258d6   5 months ago    130MB
registry.aliyuncs.com/google_containers/kube-controller-manager   v1.23.17   1dab4fc7b6e0   5 months ago    120MB
registry.aliyuncs.com/google_containers/kube-scheduler            v1.23.17   bc6794cb54ac   5 months ago    51.9MB
registry.aliyuncs.com/google_containers/kube-proxy                v1.23.17   f21c8d21558c   5 months ago    111MB
registry.aliyuncs.com/google_containers/etcd                      3.5.6-0    fce326961ae2   8 months ago    299MB
registry.aliyuncs.com/google_containers/coredns                   v1.8.6     a4ca41631cc7   22 months ago   46.8MB
registry.aliyuncs.com/google_containers/pause                     3.6        6270bb605e12   24 months ago   683kB

查看:

[root@k8sm1 ~]# kubectl get all -n kube-flannel
NAME                        READY   STATUS    RESTARTS   AGE
pod/kube-flannel-ds-bq4f8   1/1     Running   0          87s

NAME                             DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
daemonset.apps/kube-flannel-ds   1         1         1       1            1           <none>          74m

[root@k8sm1 ~]# kubectl get pods -n kube-system
NAME                                      READY   STATUS    RESTARTS        AGE
coredns-6d8c4cb4d-qqk4w                   1/1     Running   0               15h
coredns-6d8c4cb4d-vz5fs                   1/1     Running   0               15h
etcd-k8sm1.stonecoding.net                      1/1     Running   1 (3h21m ago)   15h
kube-apiserver-k8sm1.stonecoding.net            1/1     Running   1 (3h21m ago)   15h
kube-controller-manager-k8sm1.stonecoding.net   1/1     Running   1 (3h21m ago)   15h
kube-proxy-8k87h                          1/1     Running   1 (3h21m ago)   15h
kube-scheduler-k8sm1.stonecoding.net            1/1     Running   1 (3h21m ago)   15h

网络插件 Flannel 运行后,coredns 处于 Running 状态。

工作节点

根据初始化输出日志,在主机 k8sn1 和 k8sn2 执行加入到集群的命令:

[root@k8sn1 ~]# kubeadm join 192.168.92.140:6443 --token xo6ca1.0wa790qn33gx9mwb --discovery-token-ca-cert-hash sha256:63b42c8fda1a3a954c66de806fdd7ba32e6986b433e78f565ede301cd1b9e932
[preflight] Running pre-flight checks
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...

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 'kubectl get nodes' on the control-plane to see this node join the cluster.
[root@k8sn2 ~]# kubeadm join 192.168.92.140:6443 --token xo6ca1.0wa790qn33gx9mwb --discovery-token-ca-cert-hash sha256:63b42c8fda1a3a954c66de806fdd7ba32e6986b433e78f565ede301cd1b9e932
[preflight] Running pre-flight checks
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...

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 'kubectl get nodes' on the control-plane to see this node join the cluster.

在主机 k8sm1 上查看:

[root@k8sm1 ~]# kubectl get pods -n kube-flannel
NAME                    READY   STATUS    RESTARTS   AGE
kube-flannel-ds-5tf8x   1/1     Running   0          4m26s
kube-flannel-ds-h25mg   1/1     Running   0          7m11s
kube-flannel-ds-ncrrd   1/1     Running   0          4m37s

[root@k8sm1 ~]# kubectl get nodes
NAME              STATUS   ROLES                  AGE     VERSION
k8sm1.stonecoding.net   Ready    control-plane,master   15h     v1.23.17
k8sn1.stonecoding.net   Ready    <none>                 5m33s   v1.23.17
k8sn2.stonecoding.net   Ready    <none>                 4m41s   v1.23.17

[root@k8sm1 ~]# kubectl get pods -n kube-system -o wide
NAME                                      READY   STATUS    RESTARTS        AGE     IP               NODE              NOMINATED NODE   READINESS GATES
coredns-6d8c4cb4d-qqk4w                   1/1     Running   0               15h     10.224.0.2       k8sm1.stonecoding.net   <none>           <none>
coredns-6d8c4cb4d-vz5fs                   1/1     Running   0               15h     10.224.0.3       k8sm1.stonecoding.net   <none>           <none>
etcd-k8sm1.stonecoding.net                      1/1     Running   1 (3h31m ago)   15h     192.168.92.140   k8sm1.stonecoding.net   <none>           <none>
kube-apiserver-k8sm1.stonecoding.net            1/1     Running   1 (3h31m ago)   15h     192.168.92.140   k8sm1.stonecoding.net   <none>           <none>
kube-controller-manager-k8sm1.stonecoding.net   1/1     Running   1 (3h31m ago)   15h     192.168.92.140   k8sm1.stonecoding.net   <none>           <none>
kube-proxy-69pt2                          1/1     Running   0               5m25s   192.168.92.141   k8sn1.stonecoding.net   <none>           <none>
kube-proxy-8k87h                          1/1     Running   1 (3h31m ago)   15h     192.168.92.140   k8sm1.stonecoding.net   <none>           <none>
kube-proxy-rs82r                          1/1     Running   0               4m33s   192.168.92.142   k8sn2.stonecoding.net   <none>           <none>
kube-scheduler-k8sm1.stonecoding.net            1/1     Running   1 (3h31m ago)   15h     192.168.92.140   k8sm1.stonecoding.net   <none>           <none>

Ingress

需要安装 Ingress 控制器,以便 Kubernetes 对外提供服务。常用的 Ingress 控制器有 Ingress-Nginxopen in new windowTraefikopen in new window,这里使用 Ingress-Nginx。

注意:

部署前,需要确认工作节点的 80 和 443 端口没有被占用。

下载 Ingress-Nginx 资源配置清单文件:

[root@k8sm1 ~]# wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.6.4/deploy/static/provider/cloud/deploy.yaml -O ingress-nginx.yaml

如果直接下载失败,可以尝试使用代理下载:

[root@k8sm1 ~]# wget https://ghproxy.com/https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.6.4/deploy/static/provider/cloud/deploy.yaml -O ingress-nginx.yaml

修改资源配置清单文件中的镜像:

[root@k8sm1 ~]# sed -i "s|registry.k8s.io/ingress-nginx/controller:v1.6.4@sha256:15be4666c53052484dd2992efacf2f50ea77a78ae8aa21ccd91af6baaa7ea22f|registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller:v1.6.4|g" ingress-nginx.yaml 
[root@k8sm1 ~]# sed -i "s|registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20220916-gd32f8c343@sha256:39c5b2e3310dc4264d638ad28d9d1d96c4cbb2b2dcfb52368fe4e3c63f61e10f|registry.cn-hangzhou.aliyuncs.com/google_containers/kube-webhook-certgen:v20220916-gd32f8c343|g" ingress-nginx.yaml

然后调整资源配置清单文件中 ingress-nginx-controller 这个 Deployment :

  • 将 Deployment 修改为 DaemonSet
  • 增加 hostNetwork: true,将 Ingress 控制器暴露到宿主机的 80 和 443 端口
  • 增加参数 --watch-ingress-without-class=true,避免定义 IngressClass
  • 修改 dnsPolicyClusterFirstWithHostNet
  • nodeSelector 增加 ingress: nginx,将 ingress-nginx-controller 部署到指定节点上

最终的资源配置清单文件如下:

apiVersion: v1
kind: Namespace
metadata:
  labels:
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  name: ingress-nginx
---
apiVersion: v1
automountServiceAccountToken: true
kind: ServiceAccount
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.6.4
  name: ingress-nginx
  namespace: ingress-nginx
---
apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    app.kubernetes.io/component: admission-webhook
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.6.4
  name: ingress-nginx-admission
  namespace: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.6.4
  name: ingress-nginx
  namespace: ingress-nginx
rules:
- apiGroups:
  - ""
  resources:
  - namespaces
  verbs:
  - get
- apiGroups:
  - ""
  resources:
  - configmaps
  - pods
  - secrets
  - endpoints
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - services
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - networking.k8s.io
  resources:
  - ingresses
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - networking.k8s.io
  resources:
  - ingresses/status
  verbs:
  - update
- apiGroups:
  - networking.k8s.io
  resources:
  - ingressclasses
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - coordination.k8s.io
  resourceNames:
  - ingress-nginx-leader
  resources:
  - leases
  verbs:
  - get
  - update
- apiGroups:
  - coordination.k8s.io
  resources:
  - leases
  verbs:
  - create
- apiGroups:
  - ""
  resources:
  - events
  verbs:
  - create
  - patch
- apiGroups:
  - discovery.k8s.io
  resources:
  - endpointslices
  verbs:
  - list
  - watch
  - get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  labels:
    app.kubernetes.io/component: admission-webhook
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.6.4
  name: ingress-nginx-admission
  namespace: ingress-nginx
rules:
- apiGroups:
  - ""
  resources:
  - secrets
  verbs:
  - get
  - create
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  labels:
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.6.4
  name: ingress-nginx
rules:
- apiGroups:
  - ""
  resources:
  - configmaps
  - endpoints
  - nodes
  - pods
  - secrets
  - namespaces
  verbs:
  - list
  - watch
- apiGroups:
  - coordination.k8s.io
  resources:
  - leases
  verbs:
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - nodes
  verbs:
  - get
- apiGroups:
  - ""
  resources:
  - services
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - networking.k8s.io
  resources:
  - ingresses
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - events
  verbs:
  - create
  - patch
- apiGroups:
  - networking.k8s.io
  resources:
  - ingresses/status
  verbs:
  - update
- apiGroups:
  - networking.k8s.io
  resources:
  - ingressclasses
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - discovery.k8s.io
  resources:
  - endpointslices
  verbs:
  - list
  - watch
  - get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  labels:
    app.kubernetes.io/component: admission-webhook
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.6.4
  name: ingress-nginx-admission
rules:
- apiGroups:
  - admissionregistration.k8s.io
  resources:
  - validatingwebhookconfigurations
  verbs:
  - get
  - update
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.6.4
  name: ingress-nginx
  namespace: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: ingress-nginx
subjects:
- kind: ServiceAccount
  name: ingress-nginx
  namespace: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  labels:
    app.kubernetes.io/component: admission-webhook
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.6.4
  name: ingress-nginx-admission
  namespace: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: ingress-nginx-admission
subjects:
- kind: ServiceAccount
  name: ingress-nginx-admission
  namespace: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  labels:
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.6.4
  name: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: ingress-nginx
subjects:
- kind: ServiceAccount
  name: ingress-nginx
  namespace: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  labels:
    app.kubernetes.io/component: admission-webhook
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.6.4
  name: ingress-nginx-admission
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: ingress-nginx-admission
subjects:
- kind: ServiceAccount
  name: ingress-nginx-admission
  namespace: ingress-nginx
---
apiVersion: v1
data:
  allow-snippet-annotations: "true"
kind: ConfigMap
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.6.4
  name: ingress-nginx-controller
  namespace: ingress-nginx
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.6.4
  name: ingress-nginx-controller
  namespace: ingress-nginx
spec:
  externalTrafficPolicy: Local
  ipFamilies:
  - IPv4
  ipFamilyPolicy: SingleStack
  ports:
  - appProtocol: http
    name: http
    port: 80
    protocol: TCP
    targetPort: http
  - appProtocol: https
    name: https
    port: 443
    protocol: TCP
    targetPort: https
  selector:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  type: LoadBalancer
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.6.4
  name: ingress-nginx-controller-admission
  namespace: ingress-nginx
spec:
  ports:
  - appProtocol: https
    name: https-webhook
    port: 443
    targetPort: webhook
  selector:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  type: ClusterIP
---
apiVersion: apps/v1
#kind: Deployment
kind: DaemonSet
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.6.4
  name: ingress-nginx-controller
  namespace: ingress-nginx
spec:
  minReadySeconds: 0
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app.kubernetes.io/component: controller
      app.kubernetes.io/instance: ingress-nginx
      app.kubernetes.io/name: ingress-nginx
  template:
    metadata:
      labels:
        app.kubernetes.io/component: controller
        app.kubernetes.io/instance: ingress-nginx
        app.kubernetes.io/name: ingress-nginx
    spec:
      hostNetwork: true
      containers:
      - args:
        - /nginx-ingress-controller
        - --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller
        - --election-id=ingress-nginx-leader
        - --controller-class=k8s.io/ingress-nginx
        - --ingress-class=nginx
        - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
        - --validating-webhook=:8443
        - --validating-webhook-certificate=/usr/local/certificates/cert
        - --validating-webhook-key=/usr/local/certificates/key
        - --watch-ingress-without-class=true
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: LD_PRELOAD
          value: /usr/local/lib/libmimalloc.so
        image: registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller:v1.6.4
        imagePullPolicy: IfNotPresent
        lifecycle:
          preStop:
            exec:
              command:
              - /wait-shutdown
        livenessProbe:
          failureThreshold: 5
          httpGet:
            path: /healthz
            port: 10254
            scheme: HTTP
          initialDelaySeconds: 10
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        name: controller
        ports:
        - containerPort: 80
          name: http
          protocol: TCP
        - containerPort: 443
          name: https
          protocol: TCP
        - containerPort: 8443
          name: webhook
          protocol: TCP
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /healthz
            port: 10254
            scheme: HTTP
          initialDelaySeconds: 10
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        resources:
          requests:
            cpu: 100m
            memory: 90Mi
        securityContext:
          allowPrivilegeEscalation: true
          capabilities:
            add:
            - NET_BIND_SERVICE
            drop:
            - ALL
          runAsUser: 101
        volumeMounts:
        - mountPath: /usr/local/certificates/
          name: webhook-cert
          readOnly: true
      dnsPolicy: ClusterFirstWithHostNet
      nodeSelector:
        ingress: nginx
        kubernetes.io/os: linux
      serviceAccountName: ingress-nginx
      terminationGracePeriodSeconds: 300
      volumes:
      - name: webhook-cert
        secret:
          secretName: ingress-nginx-admission
---
apiVersion: batch/v1
kind: Job
metadata:
  labels:
    app.kubernetes.io/component: admission-webhook
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.6.4
  name: ingress-nginx-admission-create
  namespace: ingress-nginx
spec:
  template:
    metadata:
      labels:
        app.kubernetes.io/component: admission-webhook
        app.kubernetes.io/instance: ingress-nginx
        app.kubernetes.io/name: ingress-nginx
        app.kubernetes.io/part-of: ingress-nginx
        app.kubernetes.io/version: 1.6.4
      name: ingress-nginx-admission-create
    spec:
      containers:
      - args:
        - create
        - --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc
        - --namespace=$(POD_NAMESPACE)
        - --secret-name=ingress-nginx-admission
        env:
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        image: registry.cn-hangzhou.aliyuncs.com/google_containers/kube-webhook-certgen:v20220916-gd32f8c343
        imagePullPolicy: IfNotPresent
        name: create
        securityContext:
          allowPrivilegeEscalation: false
      nodeSelector:
        kubernetes.io/os: linux
      restartPolicy: OnFailure
      securityContext:
        fsGroup: 2000
        runAsNonRoot: true
        runAsUser: 2000
      serviceAccountName: ingress-nginx-admission
---
apiVersion: batch/v1
kind: Job
metadata:
  labels:
    app.kubernetes.io/component: admission-webhook
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.6.4
  name: ingress-nginx-admission-patch
  namespace: ingress-nginx
spec:
  template:
    metadata:
      labels:
        app.kubernetes.io/component: admission-webhook
        app.kubernetes.io/instance: ingress-nginx
        app.kubernetes.io/name: ingress-nginx
        app.kubernetes.io/part-of: ingress-nginx
        app.kubernetes.io/version: 1.6.4
      name: ingress-nginx-admission-patch
    spec:
      containers:
      - args:
        - patch
        - --webhook-name=ingress-nginx-admission
        - --namespace=$(POD_NAMESPACE)
        - --patch-mutating=false
        - --secret-name=ingress-nginx-admission
        - --patch-failure-policy=Fail
        env:
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        image: registry.cn-hangzhou.aliyuncs.com/google_containers/kube-webhook-certgen:v20220916-gd32f8c343
        imagePullPolicy: IfNotPresent
        name: patch
        securityContext:
          allowPrivilegeEscalation: false
      nodeSelector:
        kubernetes.io/os: linux
      restartPolicy: OnFailure
      securityContext:
        fsGroup: 2000
        runAsNonRoot: true
        runAsUser: 2000
      serviceAccountName: ingress-nginx-admission
---
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.6.4
  name: nginx
spec:
  controller: k8s.io/ingress-nginx
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  labels:
    app.kubernetes.io/component: admission-webhook
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.6.4
  name: ingress-nginx-admission
webhooks:
- admissionReviewVersions:
  - v1
  clientConfig:
    service:
      name: ingress-nginx-controller-admission
      namespace: ingress-nginx
      path: /networking/v1/ingresses
  failurePolicy: Fail
  matchPolicy: Equivalent
  name: validate.nginx.ingress.kubernetes.io
  rules:
  - apiGroups:
    - networking.k8s.io
    apiVersions:
    - v1
    operations:
    - CREATE
    - UPDATE
    resources:
    - ingresses
  sideEffects: None

先为需要部署 ingress-nginx-controller 的工作节点打标签:

[root@k8sm1 ~]# kubectl label nodes k8sn1.stonecoding.net ingress=nginx
node/k8sn1.stonecoding.net labeled
[root@k8sm1 ~]# kubectl label nodes k8sn2.stonecoding.net ingress=nginx
node/k8sn2.stonecoding.net labeled

部署:

[root@k8sm1 ~]# kubectl apply -f ingress-nginx.yaml
namespace/ingress-nginx created
serviceaccount/ingress-nginx created
serviceaccount/ingress-nginx-admission created
role.rbac.authorization.k8s.io/ingress-nginx created
role.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrole.rbac.authorization.k8s.io/ingress-nginx created
clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created
rolebinding.rbac.authorization.k8s.io/ingress-nginx created
rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
configmap/ingress-nginx-controller created
service/ingress-nginx-controller created
service/ingress-nginx-controller-admission created
daemonset.apps/ingress-nginx-controller created
job.batch/ingress-nginx-admission-create created
job.batch/ingress-nginx-admission-patch created
ingressclass.networking.k8s.io/nginx created
validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created

查看:

[root@k8sm1 ~]# kubectl get all -n ingress-nginx
NAME                                       READY   STATUS      RESTARTS   AGE
pod/ingress-nginx-admission-create-xltbp   0/1     Completed   0          5m54s
pod/ingress-nginx-admission-patch-gh9p5    0/1     Completed   0          5m54s
pod/ingress-nginx-controller-j49qh         1/1     Running     0          5m54s
pod/ingress-nginx-controller-whd5f         1/1     Running     0          5m54s

NAME                                         TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
service/ingress-nginx-controller             LoadBalancer   10.99.217.210   <pending>     80:31412/TCP,443:30818/TCP   5m54s
service/ingress-nginx-controller-admission   ClusterIP      10.109.220.22   <none>        443/TCP                      5m54s

NAME                                      DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR                          AGE
daemonset.apps/ingress-nginx-controller   2         2         2       2            2           ingress=nginx,kubernetes.io/os=linux   5m54s

NAME                                       COMPLETIONS   DURATION   AGE
job.batch/ingress-nginx-admission-create   1/1           4s         5m54s
job.batch/ingress-nginx-admission-patch    1/1           5s         5m54s

此时在工作节点上就可以看到监听的 80 和 443 端口:

[root@k8sn1 ~]# netstat -natp |egrep ":80|:443"
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      74480/nginx: master 
tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN      74480/nginx: master

[root@k8sn2 ~]# netstat -natp |egrep ":80|:443"
tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN      43741/nginx: master 
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      43741/nginx: master

控制平台节点已经安装了 Nginx,在 Nginx 上配置业务流量的反向代理,凡是访问 *.stonecoding.net 域的请求,都代理到工作节点映射出来的 80 端口,然后由 Ingress 根据服务名称分发到对应的 Pod。

[root@k8sm1 ~]# vi /etc/nginx/conf.d/stonecoding.net.conf
upstream default_backend_ingress {
    server 192.168.92.141:80    max_fails=3 fail_timeout=10s;
    server 192.168.92.142:80    max_fails=3 fail_timeout=10s;
}
server {
    server_name *.stonecoding.net;
    client_max_body_size 100m;

    location / {
        proxy_pass http://default_backend_ingress;
        proxy_set_header Host $http_host;
        proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for;
    }
}

[root@k8sm1 ~]# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
[root@k8sm1 ~]# systemctl start nginx
[root@k8sm1 ~]# systemctl enable nginx
Created symlink from /etc/systemd/system/multi-user.target.wants/nginx.service to /usr/lib/systemd/system/nginx.service.

Dashboard

安装 Dashboard ,使用图形界面管理 Kubernetes。

获取资源配置清单文件:

[root@k8sm1 ~]# wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.5.1/aio/deploy/recommended.yaml -O dashboard.yaml

如果直接下载失败,可以尝试使用代理下载:

[root@k8sm1 ~]# wget https://ghproxy.com/https://raw.githubusercontent.com/kubernetes/dashboard/v2.5.1/aio/deploy/recommended.yaml -O dashboard.yaml

修改资源配置清单文件,将 kubernetes-dashboard 这个 Service 的端口类型修改为 NodePort,最终的资源配置清单文件如下:

# Copyright 2017 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

apiVersion: v1
kind: Namespace
metadata:
  name: kubernetes-dashboard

---

apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard

---

kind: Service
apiVersion: v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
spec:
  ports:
    - port: 443
      targetPort: 8443
  selector:
    k8s-app: kubernetes-dashboard
  type: NodePort
---

apiVersion: v1
kind: Secret
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-certs
  namespace: kubernetes-dashboard
type: Opaque

---

apiVersion: v1
kind: Secret
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-csrf
  namespace: kubernetes-dashboard
type: Opaque
data:
  csrf: ""

---

apiVersion: v1
kind: Secret
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-key-holder
  namespace: kubernetes-dashboard
type: Opaque

---

kind: ConfigMap
apiVersion: v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-settings
  namespace: kubernetes-dashboard

---

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
rules:
  # Allow Dashboard to get, update and delete Dashboard exclusive secrets.
  - apiGroups: [""]
    resources: ["secrets"]
    resourceNames: ["kubernetes-dashboard-key-holder", "kubernetes-dashboard-certs", "kubernetes-dashboard-csrf"]
    verbs: ["get", "update", "delete"]
    # Allow Dashboard to get and update 'kubernetes-dashboard-settings' config map.
  - apiGroups: [""]
    resources: ["configmaps"]
    resourceNames: ["kubernetes-dashboard-settings"]
    verbs: ["get", "update"]
    # Allow Dashboard to get metrics.
  - apiGroups: [""]
    resources: ["services"]
    resourceNames: ["heapster", "dashboard-metrics-scraper"]
    verbs: ["proxy"]
  - apiGroups: [""]
    resources: ["services/proxy"]
    resourceNames: ["heapster", "http:heapster:", "https:heapster:", "dashboard-metrics-scraper", "http:dashboard-metrics-scraper"]
    verbs: ["get"]

---

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
rules:
  # Allow Metrics Scraper to get metrics from the Metrics server
  - apiGroups: ["metrics.k8s.io"]
    resources: ["pods", "nodes"]
    verbs: ["get", "list", "watch"]

---

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: kubernetes-dashboard
subjects:
  - kind: ServiceAccount
    name: kubernetes-dashboard
    namespace: kubernetes-dashboard

---

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: kubernetes-dashboard
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: kubernetes-dashboard
subjects:
  - kind: ServiceAccount
    name: kubernetes-dashboard
    namespace: kubernetes-dashboard

---

kind: Deployment
apiVersion: apps/v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: kubernetes-dashboard
  template:
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
    spec:
      securityContext:
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: kubernetes-dashboard
          image: kubernetesui/dashboard:v2.5.1
          imagePullPolicy: Always
          ports:
            - containerPort: 8443
              protocol: TCP
          args:
            - --auto-generate-certificates
            - --namespace=kubernetes-dashboard
            # Uncomment the following line to manually specify Kubernetes API server Host
            # If not specified, Dashboard will attempt to auto discover the API server and connect
            # to it. Uncomment only if the default does not work.
            # - --apiserver-host=http://my-address:port
          volumeMounts:
            - name: kubernetes-dashboard-certs
              mountPath: /certs
              # Create on-disk volume to store exec logs
            - mountPath: /tmp
              name: tmp-volume
          livenessProbe:
            httpGet:
              scheme: HTTPS
              path: /
              port: 8443
            initialDelaySeconds: 30
            timeoutSeconds: 30
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            runAsUser: 1001
            runAsGroup: 2001
      volumes:
        - name: kubernetes-dashboard-certs
          secret:
            secretName: kubernetes-dashboard-certs
        - name: tmp-volume
          emptyDir: {}
      serviceAccountName: kubernetes-dashboard
      nodeSelector:
        "kubernetes.io/os": linux
      # Comment the following tolerations if Dashboard must not be deployed on master
      tolerations:
        - key: node-role.kubernetes.io/master
          effect: NoSchedule

---

kind: Service
apiVersion: v1
metadata:
  labels:
    k8s-app: dashboard-metrics-scraper
  name: dashboard-metrics-scraper
  namespace: kubernetes-dashboard
spec:
  ports:
    - port: 8000
      targetPort: 8000
  selector:
    k8s-app: dashboard-metrics-scraper

---

kind: Deployment
apiVersion: apps/v1
metadata:
  labels:
    k8s-app: dashboard-metrics-scraper
  name: dashboard-metrics-scraper
  namespace: kubernetes-dashboard
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: dashboard-metrics-scraper
  template:
    metadata:
      labels:
        k8s-app: dashboard-metrics-scraper
    spec:
      securityContext:
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: dashboard-metrics-scraper
          image: kubernetesui/metrics-scraper:v1.0.7
          ports:
            - containerPort: 8000
              protocol: TCP
          livenessProbe:
            httpGet:
              scheme: HTTP
              path: /
              port: 8000
            initialDelaySeconds: 30
            timeoutSeconds: 30
          volumeMounts:
          - mountPath: /tmp
            name: tmp-volume
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            runAsUser: 1001
            runAsGroup: 2001
      serviceAccountName: kubernetes-dashboard
      nodeSelector:
        "kubernetes.io/os": linux
      # Comment the following tolerations if Dashboard must not be deployed on master
      tolerations:
        - key: node-role.kubernetes.io/master
          effect: NoSchedule
      volumes:
        - name: tmp-volume
          emptyDir: {}

部署:

[root@k8sm1 ~]# kubectl apply -f dashboard.yaml 
namespace/kubernetes-dashboard created
serviceaccount/kubernetes-dashboard created
service/kubernetes-dashboard created
secret/kubernetes-dashboard-certs created
secret/kubernetes-dashboard-csrf created
secret/kubernetes-dashboard-key-holder created
configmap/kubernetes-dashboard-settings created
role.rbac.authorization.k8s.io/kubernetes-dashboard created
clusterrole.rbac.authorization.k8s.io/kubernetes-dashboard created
rolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created
clusterrolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created
deployment.apps/kubernetes-dashboard created
service/dashboard-metrics-scraper created
deployment.apps/dashboard-metrics-scraper created

创建 dashboard-serviceaccount 服务账户:

[root@k8sm1 ~]# kubectl create serviceaccount dashboard-serviceaccount -n kubernetes-dashboard
serviceaccount/dashboard-serviceaccount created

为 dashboard-serviceaccount 服务账户授予 cluster-admin 角色:

[root@k8sm1 ~]# kubectl create clusterrolebinding dashboard-cluster-admin --clusterrole=cluster-admin --serviceaccount=kubernetes-dashboard:dashboard-serviceaccount
clusterrolebinding.rbac.authorization.k8s.io/dashboard-cluster-admin created

查看:

[root@k8sm1 ~]# kubectl get all -n kubernetes-dashboard
NAME                                             READY   STATUS    RESTARTS   AGE
pod/dashboard-metrics-scraper-799d786dbf-bm5gt   1/1     Running   0          5m32s
pod/kubernetes-dashboard-fb8648fd9-t4c7k         1/1     Running   0          5m32s

NAME                                TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)         AGE
service/dashboard-metrics-scraper   ClusterIP   10.110.86.109   <none>        8000/TCP        5m32s
service/kubernetes-dashboard        NodePort    10.107.3.191    <none>        443:32490/TCP   5m32s

NAME                                        READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/dashboard-metrics-scraper   1/1     1            1           5m32s
deployment.apps/kubernetes-dashboard        1/1     1            1           5m32s

NAME                                                   DESIRED   CURRENT   READY   AGE
replicaset.apps/dashboard-metrics-scraper-799d786dbf   1         1         1       5m32s
replicaset.apps/kubernetes-dashboard-fb8648fd9         1         1         1       5m32s

获取 token:

[root@k8sm1 ~]# kubectl describe secrets -n kubernetes-dashboard $(kubectl -n kubernetes-dashboard get secret | awk '/dashboard-serviceaccount/{print $1}')
Name:         dashboard-serviceaccount-token-n4222
Namespace:    kubernetes-dashboard
Labels:       <none>
Annotations:  kubernetes.io/service-account.name: dashboard-serviceaccount
              kubernetes.io/service-account.uid: 6ec8d67b-14b6-4369-a330-4b80eda90f25

Type:  kubernetes.io/service-account-token

Data
====
ca.crt:     1099 bytes
namespace:  20 bytes
token:      eyJhbGciOiJSUzI1NiIsImtpZCI6Ijc4NndjNWJSUjYxMDZEcmU3amtGak1OYXpwODFuX2RYQWFQeVVBOEpDRFEifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJkYXNoYm9hcmQtc2VydmljZWFjY291bnQtdG9rZW4tbjQyMjIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGFzaGJvYXJkLXNlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiNmVjOGQ2N2ItMTRiNi00MzY5LWEzMzAtNGI4MGVkYTkwZjI1Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Omt1YmVybmV0ZXMtZGFzaGJvYXJkOmRhc2hib2FyZC1zZXJ2aWNlYWNjb3VudCJ9.rq3Z0YvWBaJEn3YJTuD83mXQBgrwJ2bK8XpoFFC592XuFZr4pqvjis54QezAsgvJH4MNkkCeXWYG4w9LjOeWl10v3PqJxqWfzYA8AkFLOnIARJ0f4MoeW0nwKx8hotPVaa5iDa84TaytWwTUCC6PjUtFMx_B-RNgJnsYEvkvv7ImvSFMhLcypHieouEnLCTG7977MR9U7M2zqzeao7r-tIph6AeMCoaSwpKk0QXWNb4tuWLhdNTmKKWUdQamF0k9UE6Vl6Nz_ZkZMGtueCkctj_3pAucLl6_AYgeFjltC1WC_QGrSWg_dL8D9OjobxfMp929LvQ6hqjmpUGm-tOeGw

使用 Firefox 浏览器访问 https://192.168.92.141:32490open in new window 或者 https://192.168.92.142:32490open in new window,输入 token,点击 “登录”:

image-20230822102712609

切换命名空间为 kube-system,点击 “工作负载”,可以看到各类工作负载的运行情况:

image-20230822103247283

资源

在 Kubernetes 中,一切皆资源,管理 Kubernetes 就是管理 Kubernetes 中的资源。

[root@k8sm1 ~]# kubectl api-resources
NAME                              SHORTNAMES   APIVERSION                             NAMESPACED   KIND
bindings                                       v1                                     true         Binding
componentstatuses                 cs           v1                                     false        ComponentStatus
configmaps                        cm           v1                                     true         ConfigMap
endpoints                         ep           v1                                     true         Endpoints
events                            ev           v1                                     true         Event
limitranges                       limits       v1                                     true         LimitRange
namespaces                        ns           v1                                     false        Namespace
nodes                             no           v1                                     false        Node
persistentvolumeclaims            pvc          v1                                     true         PersistentVolumeClaim
persistentvolumes                 pv           v1                                     false        PersistentVolume
pods                              po           v1                                     true         Pod
podtemplates                                   v1                                     true         PodTemplate
replicationcontrollers            rc           v1                                     true         ReplicationController
resourcequotas                    quota        v1                                     true         ResourceQuota
secrets                                        v1                                     true         Secret
serviceaccounts                   sa           v1                                     true         ServiceAccount
services                          svc          v1                                     true         Service
mutatingwebhookconfigurations                  admissionregistration.k8s.io/v1        false        MutatingWebhookConfiguration
validatingwebhookconfigurations                admissionregistration.k8s.io/v1        false        ValidatingWebhookConfiguration
customresourcedefinitions         crd,crds     apiextensions.k8s.io/v1                false        CustomResourceDefinition
apiservices                                    apiregistration.k8s.io/v1              false        APIService
controllerrevisions                            apps/v1                                true         ControllerRevision
daemonsets                        ds           apps/v1                                true         DaemonSet
deployments                       deploy       apps/v1                                true         Deployment
replicasets                       rs           apps/v1                                true         ReplicaSet
statefulsets                      sts          apps/v1                                true         StatefulSet
tokenreviews                                   authentication.k8s.io/v1               false        TokenReview
localsubjectaccessreviews                      authorization.k8s.io/v1                true         LocalSubjectAccessReview
selfsubjectaccessreviews                       authorization.k8s.io/v1                false        SelfSubjectAccessReview
selfsubjectrulesreviews                        authorization.k8s.io/v1                false        SelfSubjectRulesReview
subjectaccessreviews                           authorization.k8s.io/v1                false        SubjectAccessReview
horizontalpodautoscalers          hpa          autoscaling/v2                         true         HorizontalPodAutoscaler
cronjobs                          cj           batch/v1                               true         CronJob
jobs                                           batch/v1                               true         Job
certificatesigningrequests        csr          certificates.k8s.io/v1                 false        CertificateSigningRequest
leases                                         coordination.k8s.io/v1                 true         Lease
endpointslices                                 discovery.k8s.io/v1                    true         EndpointSlice
events                            ev           events.k8s.io/v1                       true         Event
flowschemas                                    flowcontrol.apiserver.k8s.io/v1beta2   false        FlowSchema
prioritylevelconfigurations                    flowcontrol.apiserver.k8s.io/v1beta2   false        PriorityLevelConfiguration
ingressclasses                                 networking.k8s.io/v1                   false        IngressClass
ingresses                         ing          networking.k8s.io/v1                   true         Ingress
networkpolicies                   netpol       networking.k8s.io/v1                   true         NetworkPolicy
runtimeclasses                                 node.k8s.io/v1                         false        RuntimeClass
poddisruptionbudgets              pdb          policy/v1                              true         PodDisruptionBudget
podsecuritypolicies               psp          policy/v1beta1                         false        PodSecurityPolicy
clusterrolebindings                            rbac.authorization.k8s.io/v1           false        ClusterRoleBinding
clusterroles                                   rbac.authorization.k8s.io/v1           false        ClusterRole
rolebindings                                   rbac.authorization.k8s.io/v1           true         RoleBinding
roles                                          rbac.authorization.k8s.io/v1           true         Role
priorityclasses                   pc           scheduling.k8s.io/v1                   false        PriorityClass
csidrivers                                     storage.k8s.io/v1                      false        CSIDriver
csinodes                                       storage.k8s.io/v1                      false        CSINode
csistoragecapacities                           storage.k8s.io/v1beta1                 true         CSIStorageCapacity
storageclasses                    sc           storage.k8s.io/v1                      false        StorageClass
volumeattachments                              storage.k8s.io/v1                      false        VolumeAttachment

常用的资源:

类别名称
集群Node、Namespace
负载Pod、ReplicaSet、Deployment、StatefulSet、DaemonSet、Job、CronJob
服务Service、Ingress
配置ConfigMap、Secret、HorizontalPodAutoscaling、CustomResourceDefinition
存储Volume、PersistentVolume、LimitRange
身份ServiceAccount、Role、ClusterRole、RoleBinding、ClusterRoleBinding

Namespace

Namespace 是 Kubernetes 的命名空间,用于分组命名空间级别的资源对象。可以为不同的项目创建对应的命名空间。

支持命名空间的资源有:

[root@k8sm1 ~]# kubectl api-resources --namespaced=true
NAME                        SHORTNAMES   APIVERSION                     NAMESPACED   KIND
bindings                                 v1                             true         Binding
configmaps                  cm           v1                             true         ConfigMap
endpoints                   ep           v1                             true         Endpoints
events                      ev           v1                             true         Event
limitranges                 limits       v1                             true         LimitRange
persistentvolumeclaims      pvc          v1                             true         PersistentVolumeClaim
pods                        po           v1                             true         Pod
podtemplates                             v1                             true         PodTemplate
replicationcontrollers      rc           v1                             true         ReplicationController
resourcequotas              quota        v1                             true         ResourceQuota
secrets                                  v1                             true         Secret
serviceaccounts             sa           v1                             true         ServiceAccount
services                    svc          v1                             true         Service
controllerrevisions                      apps/v1                        true         ControllerRevision
daemonsets                  ds           apps/v1                        true         DaemonSet
deployments                 deploy       apps/v1                        true         Deployment
replicasets                 rs           apps/v1                        true         ReplicaSet
statefulsets                sts          apps/v1                        true         StatefulSet
localsubjectaccessreviews                authorization.k8s.io/v1        true         LocalSubjectAccessReview
horizontalpodautoscalers    hpa          autoscaling/v2                 true         HorizontalPodAutoscaler
cronjobs                    cj           batch/v1                       true         CronJob
jobs                                     batch/v1                       true         Job
leases                                   coordination.k8s.io/v1         true         Lease
endpointslices                           discovery.k8s.io/v1            true         EndpointSlice
events                      ev           events.k8s.io/v1               true         Event
ingresses                   ing          networking.k8s.io/v1           true         Ingress
networkpolicies             netpol       networking.k8s.io/v1           true         NetworkPolicy
poddisruptionbudgets        pdb          policy/v1                      true         PodDisruptionBudget
rolebindings                             rbac.authorization.k8s.io/v1   true         RoleBinding
roles                                    rbac.authorization.k8s.io/v1   true         Role
csistoragecapacities                     storage.k8s.io/v1beta1         true         CSIStorageCapacity

查看所有命名空间:

[root@k8sm1 ~]# kubectl get namespaces
NAME                   STATUS   AGE
default                Active   24h
ingress-nginx          Active   166m
kube-flannel           Active   24h
kube-node-lease        Active   24h
kube-public            Active   24h
kube-system            Active   24h
kubernetes-dashboard   Active   119m

其中:

  • default:默认的命名空间,在创建资源时,如果不指定命名空间,则会创建在此命名空间中。
  • kube-system:集群管理相关组件所在的命名空间。

查看命名空间的 Label:

[root@k8sm1 ~]# kubectl get namespaces --show-labels
NAME                   STATUS   AGE    LABELS
default                Active   24h    kubernetes.io/metadata.name=default
ingress-nginx          Active   166m   app.kubernetes.io/instance=ingress-nginx,app.kubernetes.io/name=ingress-nginx,kubernetes.io/metadata.name=ingress-nginx
kube-flannel           Active   24h    k8s-app=flannel,kubernetes.io/metadata.name=kube-flannel,pod-security.kubernetes.io/enforce=privileged
kube-node-lease        Active   24h    kubernetes.io/metadata.name=kube-node-lease
kube-public            Active   24h    kubernetes.io/metadata.name=kube-public
kube-system            Active   24h    kubernetes.io/metadata.name=kube-system
kubernetes-dashboard   Active   119m   kubernetes.io/metadata.name=kubernetes-dashboard

创建命名空间:

[root@k8sm1 ~]# kubectl create namespace app
namespace/app created

使用资源配置清单文件创建命名空间:

apiVersion: v1
kind: Namespace
metadata:
  name: app

字段含义如下:

名称类型是否必要说明
apiVersionStringRequiredAPI 的版本,使用 kubectl api-resources 命令查询
kindStringRequired资源类型,这里为 Namespace
metadataObjectRequired元数据
metadata.nameStringRequiredNamespace 的名字

Pod

Pod 是 Kubernetes 最小部署和调度单位,运行在物理节点上。

架构

一个 Pod 包含一个或者多个容器,共享文件系统和网络。

pod

Dockeropen in new window 中,我们知道容器之间是隔离的,有自己的网络和文件系统,为实现 Pod 中多个容器共享文件系统和网络,Kubernetes 使用了一个称为 Pause 的容器:

image-20230822155536022

[root@k8sm1 ~]# docker ps --filter "name=flannel"
CONTAINER ID   IMAGE                                               COMMAND                  CREATED             STATUS             PORTS     NAMES
3e28576512e7   d73868a08083                                        "/opt/bin/flanneld -…"   About an hour ago   Up About an hour             k8s_kube-flannel_kube-flannel-ds-h25mg_kube-flannel_f94326b5-c1e4-408e-9dd5-7af526f5d059_2
d5c27e570141   registry.aliyuncs.com/google_containers/pause:3.6   "/pause"                 About an hour ago   Up About an hour             k8s_POD_kube-flannel-ds-h25mg_kube-flannel_f94326b5-c1e4-408e-9dd5-7af526f5d059_2

Pause 容器的作用主要有 2 点:

  • 在 Pod 中作为 Linux Namespace 共享的基础容器
  • 在 PID Namespace 共享的前提下,作为每个 Pod 中的 PID 1,回收僵尸进程

下面来模拟多个容器使用 Pause 容器共享资源,先创建一个 Pause 容器:

[root@k8sm1 ~]# docker run -d --name pause --ipc=shareable -p 8080:80 registry.aliyuncs.com/google_containers/pause:3.6
0ef2e7ba8946da71166ae6b8374c7b837bb9360c49c682440c021d6588cb31ca

其中:

  • -p 8080:80:将容器的 80 端口映射到主机的 8080 端口

  • --ipc=shareable:拥有私有的 IPC 命名空间, 且可以共享给其他容器

再创建一个 Nginx 容器:

[root@k8sm1 ~]# cat <<EOF >> nginx.conf
error_log stderr;
events { worker_connections  1024; }
http {
    access_log /dev/stdout combined;
    server {
        listen 80 default_server;
        server_name example.com www.example.com;
        location / {
            proxy_pass http://127.0.0.1:2368;
        }
    }
}
EOF

[root@k8sm1 ~]# docker run -d --name nginx -v /root/nginx.conf:/etc/nginx/nginx.conf --net=container:pause --ipc=container:pause --pid=container:pause nginx
cc600daa1bf90cbdb75ad0b174b45259b9354061379cefdda4ccd443b51107a1

其中:

  • --net:指定共享 Pause 的网络

  • --ipc:指定共享 Pause 的 IPC

  • --pid:指定共享 Pause 的 PID

再创建一个 Ghost 容器:

[root@k8sm1 ~]# docker run -d --name ghost --net=container:pause --ipc=container:pause --pid=container:pause ghost:2
448b9f646d6e45c54723959ab41cde6cc32a599f710f2984ef3d0a6780d057e5

完成后,在浏览器中访问 http://192.168.92.140:8080/,发现打开了open in new window Ghost 网页。

从上面的步骤可知:

  • Pause 容器将内部 80 端口映射到宿主机 8080 端口并共享网络。
  • Nginx 容器使用 --net=container:pause 加入到 Pause 网络中。
  • Ghost 容器使用 --net=container:pause 加入到 Pause 网络中。
  • 这样三个容器共享网络后,互相之间就可以使用 localhost 直接通信。
  • --ipc=container:pause--pid=container:pause 使三个容器的 IPC 和 PID 处于同一个 Namespace中,Init 进程为 Pause,PID 为 1。
[root@k8sm1 ~]# docker exec -it nginx /bin/bash
root@0ef2e7ba8946:/# pidof pause
1
[root@k8sm1 ~]# docker exec -it ghost /bin/bash
root@0ef2e7ba8946:/var/lib/ghost# pidof pause
1

image-20230823113904286

网络

每个 Pod 都会被分配一个唯一的 IP 地址。Pod 中的所有容器共享网络空间,包括 IP 地址和端口,可以使用 localhost 互相通信。

同一宿主机上的 Pod 通过 cni0 进行通信,访问流程如下:

image-20230824101839466

  1. Pod 1 (10.244.1.2) 向 Pod 2 (10.244.1.3) 发送数据,通过 eth0 接口离开 Pod 1。
  2. 数据通过 veth0 到达 cni0 ,寻找 Pod 2 的地址。
  3. 数据通过 veth1 离开 cni0 ,发往 Pod 2。
  4. 数据通过 eth1 到达 Pod 2。

不同宿主机上的 Pod 通信,则需要使用 CNI 网络插件,这里使用的是 Flannel。

Flannel 的部署文件中包含一个 ConfigMap,其中包含 Flannel CNI 的配置文件以及 Network 和 Backend 配置。Flannel 通过 DaemonSet 调度到每个节点上。Flannel 的 Pod 有一个 Init 容器,负责将 ConfigMap 中的 cni-conf.json 复制到宿主机上的 /etc/cni/net.d/10-flannel.conflist

[root@k8sm1 ~]# cat /etc/cni/net.d/10-flannel.conflist
{
  "name": "cbr0",
  "cniVersion": "0.3.1",
  "plugins": [
    {
      "type": "flannel",
      "delegate": {
        "hairpinMode": true,
        "isDefaultGateway": true
      }
    },
    {
      "type": "portmap",
      "capabilities": {
        "portMappings": true
      }
    }
  ]
}

Flannel 会根据 ConfigMap 中的 Network 来为每个 Node 分配单独的子网,这样就能保证不同 Node 上的 Pod IP 不会冲突。并将集群网络和 Subnet 的配置信息写入文件 /run/flannel/subnet.env

[root@k8sm1 ~]# cat /run/flannel/subnet.env
FLANNEL_NETWORK=10.244.0.0/16
FLANNEL_SUBNET=10.244.0.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=true

一般配置 ConfigMap 中的 Backend 为 vxlan(使用类似 VLAN 的封装技术将 OSI 第 2 层以太网帧封装在第 4 层 UDP 数据报中,并使用 4789 作为默认 IANA 分配的目标 UDP 端口号),宿主机会增加 flannel.1 网口,作为 Pod 与外部网络的网关。

3 个节点的网络和路由情况如下:

[root@k8sm1 ~]# ip addr | egrep 'mtu|link|inet'
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
    inet6 ::1/128 scope host 
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:fa:5b:e5 brd ff:ff:ff:ff:ff:ff
    inet 192.168.92.140/24 brd 192.168.92.255 scope global ens33
    inet6 fe80::20c:29ff:fefa:5be5/64 scope link 
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:8e:8d:41:6b brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
    inet6 fe80::42:8eff:fe8d:416b/64 scope link 
4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default 
    link/ether fa:9d:c6:a2:46:3a brd ff:ff:ff:ff:ff:ff
    inet 10.244.0.0/32 scope global flannel.1
    inet6 fe80::f89d:c6ff:fea2:463a/64 scope link 
5: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default qlen 1000
    link/ether b2:7a:c6:02:36:44 brd ff:ff:ff:ff:ff:ff
    inet 10.244.0.1/24 brd 10.244.0.255 scope global cni0
    inet6 fe80::b07a:c6ff:fe02:3644/64 scope link 
6: veth71670adb@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default 
    link/ether c2:5f:ae:2c:0b:c9 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::c05f:aeff:fe2c:bc9/64 scope link 
7: vethb988663a@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default 
    link/ether 0e:48:71:3a:1b:bc brd ff:ff:ff:ff:ff:ff link-netnsid 1
    inet6 fe80::c48:71ff:fe3a:1bbc/64 scope link 
15: vetha420398@if14: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether 62:9b:9f:84:c6:73 brd ff:ff:ff:ff:ff:ff link-netnsid 2
    inet6 fe80::609b:9fff:fe84:c673/64 scope link 

[root@k8sm1 ~]# ip route
default via 192.168.92.2 dev ens33 
10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1 
10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink 
10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink 
169.254.0.0/16 dev ens33 scope link metric 1002 
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 
192.168.92.0/24 dev ens33 proto kernel scope link src 192.168.92.140 
[root@k8sn1 ~]# ip addr | egrep 'mtu|link|inet'
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
    inet6 ::1/128 scope host 
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:63:91:cc brd ff:ff:ff:ff:ff:ff
    inet 192.168.92.141/24 brd 192.168.92.255 scope global ens33
    inet6 fe80::20c:29ff:fe63:91cc/64 scope link 
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:d3:ab:d7:ba brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default 
    link/ether a2:ad:25:ef:4d:9b brd ff:ff:ff:ff:ff:ff
    inet 10.244.1.0/32 scope global flannel.1
    inet6 fe80::a0ad:25ff:feef:4d9b/64 scope link 
5: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default qlen 1000
    link/ether f6:71:9e:02:b3:8f brd ff:ff:ff:ff:ff:ff
    inet 10.244.1.1/24 brd 10.244.1.255 scope global cni0
    inet6 fe80::f471:9eff:fe02:b38f/64 scope link 
6: veth3c45aab8@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default 
    link/ether 86:ba:d2:db:17:d8 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::84ba:d2ff:fedb:17d8/64 scope link 
7: veth9f1c97d6@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default 
    link/ether fa:2e:28:2b:1c:11 brd ff:ff:ff:ff:ff:ff link-netnsid 1
    inet6 fe80::f82e:28ff:fe2b:1c11/64 scope link

[root@k8sn1 ~]# ip -d link show flannel.1
4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default 
    link/ether a2:ad:25:ef:4d:9b brd ff:ff:ff:ff:ff:ff promiscuity 0 
    vxlan id 1 local 192.168.92.141 dev ens33 srcport 0 0 dstport 8472 nolearning ageing 300 noudpcsum noudp6zerocsumtx noudp6zerocsumrx addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 

[root@k8sn1 ~]# ip route
default via 192.168.92.2 dev ens33 
10.244.0.0/24 via 10.244.0.0 dev flannel.1 onlink 
10.244.1.0/24 dev cni0 proto kernel scope link src 10.244.1.1 
10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink 
169.254.0.0/16 dev ens33 scope link metric 1002 
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 
192.168.92.0/24 dev ens33 proto kernel scope link src 192.168.92.141
[root@k8sn2 ~]# ip addr | egrep 'mtu|link|inet'
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
    inet6 ::1/128 scope host 
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:23:91:65 brd ff:ff:ff:ff:ff:ff
    inet 192.168.92.142/24 brd 192.168.92.255 scope global ens33
    inet6 fe80::20c:29ff:fe23:9165/64 scope link 
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:f2:73:8f:f1 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default 
    link/ether 5e:ae:41:8d:4b:9f brd ff:ff:ff:ff:ff:ff
    inet 10.244.2.0/32 scope global flannel.1
    inet6 fe80::5cae:41ff:fe8d:4b9f/64 scope link 
5: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default qlen 1000
    link/ether 92:7d:fb:49:42:0a brd ff:ff:ff:ff:ff:ff
    inet 10.244.2.1/24 brd 10.244.2.255 scope global cni0
    inet6 fe80::907d:fbff:fe49:420a/64 scope link 
6: veth52664c07@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default 
    link/ether 6a:ca:b4:3f:da:50 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::68ca:b4ff:fe3f:da50/64 scope link

[root@k8sn2 ~]# ip route
default via 192.168.92.2 dev ens33 
10.244.0.0/24 via 10.244.0.0 dev flannel.1 onlink 
10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink 
10.244.2.0/24 dev cni0 proto kernel scope link src 10.244.2.1 
169.254.0.0/16 dev ens33 scope link metric 1002 
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 
192.168.92.0/24 dev ens33 proto kernel scope link src 192.168.92.142

可以看到每台宿主机上都有两条使用 flannel.1 网口的路由,作为访问其他宿主机上 Pod 的网关。

Pod 跨宿主机访问流程如下:

image-20230828092040735

  1. Pod 1 (10.244.1.2) 向 Pod 4 (10.244.2.3) 发送数据。因为 Pod 1 和 Pod 4 的 IP 不在一个子网,因此走默认路由表,直接发向接口 cni0 的地址 10.244.1.1

  2. IP 包到达 cni0 接口后,根据主机路由表 10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink,下一跳是 10.244.2.0,通过 flannel.1 发送。

  3. 此时需要知道 10.244.2.0 的 MAC 地址,因此检查节点 1 的 ARP 表:

    [root@k8sn1 ~]# arp
    Address                  HWtype  HWaddress           Flags Mask            Iface
    10.244.1.6               ether   1e:a2:1e:4d:7e:11   C                     cni0
    10.244.0.0               ether   fa:9d:c6:a2:46:3a   CM                    flannel.1
    10.244.1.5               ether   62:1d:b8:54:c9:7c   C                     cni0
    10.244.2.0               ether   5e:ae:41:8d:4b:9f   CM                    flannel.1
    192.168.92.1             ether   00:50:56:c0:00:08   C                     ens33
    k8sm1                    ether   00:0c:29:fa:5b:e5   C                     ens33
    gateway                  ether   00:50:56:e9:3d:8f   C                     ens33
    

    发现 10.244.2.0 ether 5e:ae:41:8d:4b:9f CM flannel.1,因此要发送的帧如下:

    image-20230823201953717

  4. 二层帧的转发需要查找主机的 FDB 表(使用 bridge fdb show 命令查看)。这里匹配到 5e:ae:41:8d:4b:9f dev flannel.1 dst 192.168.92.142 self permanent。内核将数据封装成 vxlan 的包从 ens33 发出去。发出去的报文如下:

    image-20230823204718419

  5. 节点 2 的 ens33 收到包后,发现是 vxlan,内核对包解封装,转发到 flannel.1 上: image-20230823201953717

  6. 此时三层目标地址是 10.244.2.3,因此匹配主机的路由表 10.244.2.0/24 dev cni0 proto kernel scope link src 10.244.2.1 ,转发给 cni0

  7. cni0 和 Pod 是二层互通的,将包发给 Pod。

  8. Pod 收到包。三层的来源地址是 10.244.1.2,二层的来源地址是 cni0 的 MAC 地址。

配置

通过资源配置清单文件来创建 Pod,使用以下命令查看相关配置项:

[root@k8sm1 ~]# kubectl explain pods
[root@k8sm1 ~]# kubectl explain pods.metadata
[root@k8sm1 ~]# kubectl explain pods.spec 
[root@k8sm1 ~]# kubectl explain pods.status

语法如下:

apiVersion: v1
kind: Pod
metadata:
  name: string
  namespace: string
  labels:
    - name: string
  annotations:
    - name: string
spec:
  containers:
  - name: string
    image: string
    imagePullPolicy: [Always | Never | IfNotPresent]
    command: [string]
    args: [string]
    workingDir: string
    volumeMounts:
    - name: string
      mountPath: string
      readOnly: boolean
    ports:
    - name: string
      containerPort: int
      hostPort: int
      protocol: [TCP | UDP]
    env:
    - name: string
      value: string
    resources:
      limits:
        cpu: string
        memory: string
      requests:
        cpu: string
        memory: string
    livenessProbe:
      exec:
        command: [string]
      httpGet:
        path: string
        port: number
        host: string
        scheme: string
        httpHeaders:
        - name: string
          value: string
      tcpSocket:
        port: number
      initialDelaySeconds: 0
      timeoutSeconds: 0
      periodSeconds: 0
      successThreshold: 0
      failureThreshold: 0
    securityContext:
      provoleged: false
  restartPolicy: [Always | Never | OnFailure]
  nodeSelector: object
  imagePullSecrets:
  - name: string
  hostNetwork: false
  dnsPolicy: ClusterFirst
  hostAliases:
  - ip: "127.0.0.1"
    hostnames:
    - "foo.local"
    - "bar.local"
  volumes:
  - name: string
    emptyDir: {}
    hostPath:
      path: string
    secret:
      secretName: string
      items:
      - key: string
        path: string
    comfigMap:
      name: string
      items:
      - key: string
        path: string

字段含义如下:

名称类型是否必要说明
apiVersionStringRequiredAPI 的版本,使用 kubectl api-resources 命令查询
kindStringRequired资源类型,这里为 Pod
metadataObjectRequired元数据
metadata.nameStringRequiredPod 的名字
metadata.namespaceStringRequiredPod 的命名空间,默认为 default 命名空间
metadata.labels[]List自定义标签列表
metadata.annotation[]List自定义注解列表
SpecObjectRequiredPod 的详细定义
spec.containers[]ListRequiredPod 的容器定义
spec.containers[].nameStringRequired容器名称
spec.containers[].imageStringRequired容器的镜像名称和版本
spec.containers[].imagePullPolicyString镜像拉取策略,包括:
Always:默认值,表示每次都尝试重新下载镜像
Never:表示仅使用本地镜像
IfNotPresent:本地没有镜像才下载镜像
spec.containers[].command[]List容器的启动命令列表,如果不指定,则使用镜像的启动命令
spec.containers[].args[]List容器的启动命令参数列表
spec.containers[].workingDirString容器的工作目录
spec.containers[].volumeMounts[]List挂载到容器内部的存储卷配置
spec.containers[].volumeMounts[].nameString在 spec.volumes[] 部分定义的共享存储卷名称
spec.containers[].volumeMounts[].mountPathString存储卷在容器内挂载的绝对路径
spec.containers[].volumeMounts[].readOnlyBoolean是否为只读模式,默认值为读写模式
spec.containers[].ports[]List容器是否需要暴露的端口号列表
spec.containers[].ports[].nameString端口的名称
spec.containers[].ports[].containerPortInt容器需要监听的端口号
spec.containers[].ports[].hostPortInt容器所在宿主机需要监听的端口号,默认与 containerPort 相同。设置 hostPort 时,同一台宿主机将无法启动该容器的第二份副本
spec.containers[].ports[].protocolSrting端口协议,支持 TCP 和 UDP,默认值为 TCP
spec.containers[].env[]List容器的环境变量列表
spec.containers[].env[].nameString环境变量的名称
spec.containers[].env[].valumeString环境变量的值
spec.containers[].resourcesObject资源限制和资源请求
spec.containers[].resources.limitsObject资源限制
spec.containers[].resources.limits.cpuStringCPU 限制
spec.containers[].resources.limits.memoryString内存限制
spec.containers[].resources.requestsObject资源请求
spec.containers[].resources.requests.cpuStringCPU 请求
spec.containers[].resources.requests.memoryString内存请求
spec.containers[].livenessProbeObject容器存活探针
spec.containers[].livenessProbe.execObject使用 exec 方式对 Pod 容器进行探测
spec.containers[].livenessProbe.exec.command[]Stringexec 方式需要执行的命令或脚本
spec.containers[].livenessProbe.httpGetObject使用 httpGet 方式对 Pod 容器进行探测
spec.containers[].livenessProbe.tcpSocketObject使用 tcpSocket 方式对 Pod 容器进行探测
spec.containers[].livenessProbe.initialDelaySecondsNumber容器启动完成后到进行初次探测的时间间隔,单位为秒
spec.containers[].livenessProbe.timeoutSecondsNumber探测的超时秒数,默认值为 1 秒,超时将重启该容器
spec.containers[].livenessProbe.periodSecondsNumber定期探测时间间隔,单位为 10 秒
spec.containers[].readinessProbeObject容器就绪探针
spec.containers[].readinessProbe.execObject使用 exec 方式对 Pod 容器进行探测
spec.containers[].readinessProbe.exec.command[]Stringexec 方式需要执行的命令或脚本
spec.containers[].readinessProbe.httpGetObject使用 httpGet 方式对 Pod 容器进行探测
spec.containers[].readinessProbe.tcpSocketObject使用 tcpSocket 方式对 Pod 容器进行探测
spec.containers[].readinessProbe.initialDelaySecondsNumber容器启动完成后到进行初次探测的时间间隔,单位为秒
spec.containers[].readinessProbe.timeoutSecondsNumber探测的超时秒数,默认值为 1 秒,超时将该容器从服务中剔除
spec.containers[].readinessProbe.periodSecondsNumber定期探测时间间隔,单位为 10 秒
spec.containers[].startupProbeObject容器启动探针
spec.containers[].startupProbe.execObject使用 exec 方式对 Pod 容器进行探测
spec.containers[].startupProbe.exec.command[]Stringexec 方式需要执行的命令或脚本
spec.containers[].startupProbe.httpGetObject使用 httpGet 方式对 Pod 容器进行探测
spec.containers[].startupProbe.tcpSocketObject使用 tcpSocket 方式对 Pod 容器进行探测
spec.containers[].startupProbe.initialDelaySecondsNumber容器启动完成后到进行初次探测的时间间隔,单位为秒
spec.containers[].startupProbe.timeoutSecondsNumber探测的超时秒数,默认值为 1 秒
spec.containers[].startupProbe.periodSecondsNumber定期探测时间间隔,单位为 10 秒
spec.volumes[]List共享存储卷列表
spec.volumes[].nameString共享存储卷的名称,用于 spec.containers[].volumeMounts[].name
spec.volumes[].emptyDirObject类型为 emptyDir 的存储卷,表示与 Pod 同生命周期的一个临时目录,其值为一个空对象:emptyDir: {}
spec.volumes[].hostPathObject类型为 hostPath 的存储卷,表示挂载 Pod 所在宿主机的目录
spec.volumes[].hostPath.pathStringPod 所在宿主机的目录,将用于容器中挂载的目录
spec.volumes[].secretObject类型为 secret 的存储卷,表示挂载集群预定义的 secret 对象到容器内部
spec.volumes[].configMapObject类型为 configMap 的存储卷,表示挂载集群预定义的 configMap 对象到容器内部
spec.restartPolicyStringPod 中所有容器的重启策略,包括:
Always:默认值,始终重启
OnFailure:以非零退出码终止时才会重启
Never:不重启
spec.nodeSelectorObject表示将该 Pod 调度到包含对应 Label 的 Node 上
spec.imagePullSecretsObject拉取镜像时使用的 secret 名称
spec.hostNetworkBoolean是否使用宿主机的地址和端口
spec.dnsPolicyStringPode 的 DNS 策略,包括以下四种:
Default:从宿主机继承域名解析配置,只能解析注册到互联网上的外部域名,无法解析集群内部域名
ClusterFirst:默认策略,应用对接 Kube-DNS/CoreDNS。这种场景下,容器既能够解析 Service 注册的集群内部域名,也能够解析发布到互联网上的外部域名。不过 ClusterFirst 还有一个特例,如果 Pod 设置了hostNetwork: true,则 ClusterFirst 就会被强制转换成 Default
ClusterFirstWithHostNet:如果 Pod 设置了hostNetwork: true,则应显式设置 DNS 策略为 ClusterFirstWithHostNet
None:允许 Pod 忽略 Kubernetes 环境中的 DNS 设置,会使用其 dnsConfig 字段提供的 DNS 设置
spec.hostAliasesObject向 Pod 的 /etc/hosts 文件中添加条目

管理

使用如下资源配置清单文件定义 Pod:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: app
spec:
  containers:
  - name: nginx
    image: nginx:1.14.2
    ports:
    - containerPort: 80

创建 Pod:

[root@k8sm1 ~]# kubectl apply -f simple-pod.yaml 
pod/nginx created

查看 Pod 运行情况:

[root@k8sm1 ~]# kubectl get pods -n app
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          63s

查看 Pod 的详细信息:

[root@k8sm1 ~]# kubectl get pods -n app -o yaml
apiVersion: v1
items:
- apiVersion: v1
  kind: Pod
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"name":"nginx","namespace":"app"},"spec":{"containers":[{"image":"nginx:1.14.2","name":"nginx","ports":[{"containerPort":80}]}]}}
    creationTimestamp: "2023-08-24T05:09:58Z"
    name: nginx
    namespace: app
    resourceVersion: "134938"
    uid: 6aea2cfe-3547-4a9e-b559-d39fccb98282
  spec:
    containers:
    - image: nginx:1.14.2
      imagePullPolicy: IfNotPresent
      name: nginx
      ports:
      - containerPort: 80
        protocol: TCP
      resources: {}
      terminationMessagePath: /dev/termination-log
      terminationMessagePolicy: File
      volumeMounts:
      - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
        name: kube-api-access-glhq7
        readOnly: true
    dnsPolicy: ClusterFirst
    enableServiceLinks: true
    nodeName: k8sn2.stonecoding.net
    preemptionPolicy: PreemptLowerPriority
    priority: 0
    restartPolicy: Always
    schedulerName: default-scheduler
    securityContext: {}
    serviceAccount: default
    serviceAccountName: default
    terminationGracePeriodSeconds: 30
    tolerations:
    - effect: NoExecute
      key: node.kubernetes.io/not-ready
      operator: Exists
      tolerationSeconds: 300
    - effect: NoExecute
      key: node.kubernetes.io/unreachable
      operator: Exists
      tolerationSeconds: 300
    volumes:
    - name: kube-api-access-glhq7
      projected:
        defaultMode: 420
        sources:
        - serviceAccountToken:
            expirationSeconds: 3607
            path: token
        - configMap:
            items:
            - key: ca.crt
              path: ca.crt
            name: kube-root-ca.crt
        - downwardAPI:
            items:
            - fieldRef:
                apiVersion: v1
                fieldPath: metadata.namespace
              path: namespace
  status:
    conditions:
    - lastProbeTime: null
      lastTransitionTime: "2023-08-24T05:09:58Z"
      status: "True"
      type: Initialized
    - lastProbeTime: null
      lastTransitionTime: "2023-08-24T05:10:47Z"
      status: "True"
      type: Ready
    - lastProbeTime: null
      lastTransitionTime: "2023-08-24T05:10:47Z"
      status: "True"
      type: ContainersReady
    - lastProbeTime: null
      lastTransitionTime: "2023-08-24T05:09:58Z"
      status: "True"
      type: PodScheduled
    containerStatuses:
    - containerID: docker://1d19c07aff70c409a3d3c9b303a67853f97ce77cf76848aff5a60d7f01b898c4
      image: nginx:1.14.2
      imageID: docker-pullable://nginx@sha256:f7988fb6c02e0ce69257d9bd9cf37ae20a60f1df7563c3a2a6abe24160306b8d
      lastState: {}
      name: nginx
      ready: true
      restartCount: 0
      started: true
      state:
        running:
          startedAt: "2023-08-24T05:10:47Z"
    hostIP: 192.168.92.142
    phase: Running
    podIP: 10.244.2.6
    podIPs:
    - ip: 10.244.2.6
    qosClass: BestEffort
    startTime: "2023-08-24T05:09:58Z"
kind: List
metadata:
  resourceVersion: ""
  selfLink: ""

查看 Pod 运行信息:

[root@k8sm1 ~]# kubectl describe pods nginx -n app
Name:         nginx
Namespace:    app
Priority:     0
Node:         k8sn2.stonecoding.net/192.168.92.142
Start Time:   Thu, 24 Aug 2023 13:09:58 +0800
Labels:       <none>
Annotations:  <none>
Status:       Running
IP:           10.244.2.6
IPs:
  IP:  10.244.2.6
Containers:
  nginx:
    Container ID:   docker://1d19c07aff70c409a3d3c9b303a67853f97ce77cf76848aff5a60d7f01b898c4
    Image:          nginx:1.14.2
    Image ID:       docker-pullable://nginx@sha256:f7988fb6c02e0ce69257d9bd9cf37ae20a60f1df7563c3a2a6abe24160306b8d
    Port:           80/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Thu, 24 Aug 2023 13:10:47 +0800
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-glhq7 (ro)
Conditions:
  Type              Status
  Initialized       True 
  Ready             True 
  ContainersReady   True 
  PodScheduled      True 
Volumes:
  kube-api-access-glhq7:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
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
  ----    ------     ----   ----               -------
  Normal  Scheduled  2m19s  default-scheduler  Successfully assigned app/nginx to k8sn2.stonecoding.net
  Normal  Pulling    2m18s  kubelet            Pulling image "nginx:1.14.2"
  Normal  Pulled     91s    kubelet            Successfully pulled image "nginx:1.14.2" in 47.377873524s (47.377878779s including waiting)
  Normal  Created    90s    kubelet            Created container nginx
  Normal  Started    90s    kubelet            Started container nginx

删除 Pod:

[root@k8sm1 ~]# kubectl delete pod nginx -n app
pod "nginx" deleted

Deployment

在实际工作中,往往不会单独创建一个 Pod,而是使用控制器来创建和管理 Pod。最常用的控制器就是 Deployment,用来创建和管理无状态的 Pod。

在介绍 Deployment 控制器之前,先了解一下 ReplicaSet 控制器。ReplicaSet 的目的是维护一组 Pod,确保任何时间都有指定数量的 Pod 副本在运行,如果有容器异常退出,会自动创建新的 Pod 来替代。可以修改 ReplicaSet 资源配置清单文件中的 spec.replica 数量来创建或删除 Pod,让 Pod 数量符合要求。

Deployment 为 Pod 和 ReplicaSet 提供了一个声明式更新,用于创建或删除 Pod,让 Pod 数量符合要求。通过在 Deployment 对象中描述一个期望的状态,Deployment 就会按照一定的控制速率把实际状态改成期望状态。通过定义一个 Deployment 控制器创建一个新的 ReplicaSet 控制器,通过 ReplicaSet 控制器创建 Pod,删除 Deployment 控制器,也会删除 Deployment 控制器下对应的 ReplicaSet 控制器和 Pod 资源。

使用 Deployment 而不直接创建 ReplicaSet 是因为 Deployment 拥有 ReplicaSet 没有的特性,例如:

  • 滚动升级
  • 回滚应用

创建

使用如下资源配置清单文件 nginx-deployment.yaml 定义 Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: app
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

其中:

  • metadata.name:指定 Deployment 名称,为后续创建 ReplicaSet 和 Pod 的命名基础
  • metadata.labels:指定 Deployment 标签
  • spec.replicas:指定 Pod 数量,默认为 1
  • spec.selectorspec 的必要字段,指定 Pod 的标签选择器,必须与 spec.template.metadata.labels 匹配
  • spec.strategy:指定用新 Pod 替换旧 Pod 的策略
  • spec.strategy.type:指定策略类型,可以是 Recreate(删除当前所有 Pod,再新建)和 RollingUpdate(滚动更新),默认为 RollingUpdate
  • spec.strategy.rollingUpdate.maxSurge:指定更新过程中可以创建的超出期望的 Pod 个数,默认为 25%,例如原 Pod 为 4 个,则更新过程中,Pod 数量最多为 5 个
  • spec.strategy.rollingUpdate.maxUnavailable:指定更新过程中不可用的 Pod 的个数上限,默认为 25%,例如原 Pod 为 4 个,则更新过程中,Pod 不可用数量最多为 1 个
  • spec.templatespec 的必要字段,定义 Pod,字段含义与 Pod 的配置open in new window一致,但没有apiVersionkind 字段
  • spec.template.metadata.labels:指定 Pod 标签

创建 Deployment :

[root@k8sm1 ~]# kubectl apply -f nginx-deployment.yaml 
deployment.apps/nginx-deployment created

查看 Deployment:

[root@k8sm1 ~]# kubectl get deployments -n app
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   0/3     3            0           48s

[root@k8sm1 ~]# kubectl get deployments -n app
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3/3     3            3           2m55s

其中:

  • NAME:Deployment 的名称
  • READY:可用副本数,格式为 就绪数量/期望数量
  • UP-TO-DATE:成功更新的 Pod 数量
  • AVAILABLE:可用副本数
  • AGE:Deployment 运行时间

查看创建的 ReplicaSet:

[root@k8sm1 ~]# kubectl get replicasets -n app --show-labels
NAME                         DESIRED   CURRENT   READY   AGE   LABELS
nginx-deployment-9456bbbf9   3         3         3       21m   app=nginx,pod-template-hash=9456bbbf9

其中:

  • NAME:ReplicaSets 的名称,其格式为 [DEPLOYMENT-NAME]-[HASH],作为其 Pod 名称的基础,其 HASH 与标签 pod-template-hash 的值相同
  • DESIRED:期望副本数量,即 spec.replicas
  • CURRENT:当前运行的副本数
  • READY:用户可用的副本数
  • AGE:ReplicaSet 运行时间

查看创建的 Pod:

[root@k8sm1 ~]# kubectl get pods -n app --show-labels
NAME                               READY   STATUS    RESTARTS   AGE   LABELS
nginx-deployment-9456bbbf9-9pssm   1/1     Running   0          18m   app=nginx,pod-template-hash=9456bbbf9
nginx-deployment-9456bbbf9-gwlf8   1/1     Running   0          18m   app=nginx,pod-template-hash=9456bbbf9
nginx-deployment-9456bbbf9-lnxq8   1/1     Running   0          18m   app=nginx,pod-template-hash=9456bbbf9

可以看到每个 Pod 都有标签 pod-template-hash

查看 Deployment 详细信息:

[root@k8sm1 ~]# kubectl get deployments nginx-deployment -n app -o yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"labels":{"app":"nginx"},"name":"nginx-deployment","namespace":"app"},"spec":{"replicas":3,"selector":{"matchLabels":{"app":"nginx"}},"strategy":{"rollingUpdate":{"maxSurge":"25%","maxUnavailable":"25%"},"type":"RollingUpdate"},"template":{"metadata":{"labels":{"app":"nginx"}},"spec":{"containers":[{"image":"nginx:1.14.2","name":"nginx","ports":[{"containerPort":80}]}]}}}}
  creationTimestamp: "2023-08-25T05:49:21Z"
  generation: 1
  labels:
    app: nginx
  name: nginx-deployment
  namespace: app
  resourceVersion: "173682"
  uid: 976e6f8c-f5e9-4691-9316-a8ff20b09533
spec:
  progressDeadlineSeconds: 600
  replicas: 3
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: nginx
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:1.14.2
        imagePullPolicy: IfNotPresent
        name: nginx
        ports:
        - containerPort: 80
          protocol: TCP
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
status:
  availableReplicas: 3
  conditions:
  - lastTransitionTime: "2023-08-25T05:49:23Z"
    lastUpdateTime: "2023-08-25T05:49:23Z"
    message: Deployment has minimum availability.
    reason: MinimumReplicasAvailable
    status: "True"
    type: Available
  - lastTransitionTime: "2023-08-25T05:49:21Z"
    lastUpdateTime: "2023-08-25T05:49:23Z"
    message: ReplicaSet "nginx-deployment-9456bbbf9" has successfully progressed.
    reason: NewReplicaSetAvailable
    status: "True"
    type: Progressing
  observedGeneration: 1
  readyReplicas: 3
  replicas: 3
  updatedReplicas: 3

更新

只有 Deployment 的 Pod 模版(spec.template)发生改变才会触发 Rollout,例如更新了容器镜像的版本,其它诸如扩容 Deployment,不会触发 Rollout。

更新 Deployment,使用 nginx:1.16.1 镜像替换 nginx:1.14.2

[root@k8sm1 ~]# kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1 -n app
deployment.apps/nginx-deployment image updated

也可以使用以下命令直接编辑资源配置清单或者修改资源配置清单文件后再 Apply:

[root@k8sm1 ~]# kubectl edit deployment/nginx-deployment -n app

查看 Rollout 状态:

[root@k8sm1 ~]# kubectl rollout status deployment/nginx-deployment -n app
Waiting for deployment "nginx-deployment" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "nginx-deployment" rollout to finish: 1 old replicas are pending termination...
deployment "nginx-deployment" successfully rolled out

查看 Deployment 变化,可以看到 UP-TO-DATE 从 1 变到 3:

[root@k8sm1 ~]# kubectl get deployments -n app              
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3/3     1            3           77m

[root@k8sm1 ~]# kubectl get deployments -n app              
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3/3     2            3           77m

[root@k8sm1 ~]# kubectl get deployments -n app 
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3/3     3            3           80m

查看 ReplicaSet 变化,可以看到创建了一个新的 ReplicaSet,先在新的 ReplicaSet 中创建一个(spec.strategy.rollingUpdate.maxSurge)新版本的 Pod,再在老的 ReplicaSet 中删除一个(spec.strategy.rollingUpdate.maxUnavailable)旧版本的 Pod,直到新的 ReplicaSet 中的 Pod 数量达到设定值,老的 ReplicaSet 中的Pod 数量变为 0:

[root@k8sm1 ~]# kubectl get replicasets -n app --show-labels
NAME                         DESIRED   CURRENT   READY   AGE   LABELS
nginx-deployment-9456bbbf9   3         3         3       76m   app=nginx,pod-template-hash=9456bbbf9

[root@k8sm1 ~]# kubectl get replicasets -n app --show-labels
NAME                         DESIRED   CURRENT   READY   AGE   LABELS
nginx-deployment-9456bbbf9   3         3         3       77m   app=nginx,pod-template-hash=9456bbbf9
nginx-deployment-ff6655784   1         1         0       15s   app=nginx,pod-template-hash=ff6655784

[root@k8sm1 ~]# kubectl get replicasets -n app --show-labels
NAME                         DESIRED   CURRENT   READY   AGE   LABELS
nginx-deployment-9456bbbf9   2         2         2       77m   app=nginx,pod-template-hash=9456bbbf9
nginx-deployment-ff6655784   2         2         1       32s   app=nginx,pod-template-hash=ff6655784

[root@k8sm1 ~]# kubectl get replicasets -n app --show-labels
NAME                         DESIRED   CURRENT   READY   AGE   LABELS
nginx-deployment-9456bbbf9   0         0         0       77m   app=nginx,pod-template-hash=9456bbbf9
nginx-deployment-ff6655784   3         3         3       48s   app=nginx,pod-template-hash=ff6655784

查看 Pod 变化,可以看到新版本 Pod 的创建和老版本 Pod 的删除,只有在足够数量的新版本 Pod 创建成功后才会删除老版本 Pod,也只有在足够数量的老版本 Pod 删除成功后才会再去创建新版本 Pod,以确保至少有 3 个 Pod 可用,最多有 4 个 Pod 可用,也就是可用 Pod 数量在 replicas - maxUnavailablereplicas + maxSurge 之间:

[root@k8sm1 ~]# kubectl get pods -n app --show-labels
NAME                               READY   STATUS    RESTARTS   AGE   LABELS
nginx-deployment-9456bbbf9-9pssm   1/1     Running   0          76m   app=nginx,pod-template-hash=9456bbbf9
nginx-deployment-9456bbbf9-gwlf8   1/1     Running   0          76m   app=nginx,pod-template-hash=9456bbbf9
nginx-deployment-9456bbbf9-lnxq8   1/1     Running   0          76m   app=nginx,pod-template-hash=9456bbbf9

[root@k8sm1 ~]# kubectl get pods -n app --show-labels       
NAME                               READY   STATUS              RESTARTS   AGE   LABELS
nginx-deployment-9456bbbf9-9pssm   1/1     Running             0          77m   app=nginx,pod-template-hash=9456bbbf9
nginx-deployment-9456bbbf9-gwlf8   1/1     Running             0          77m   app=nginx,pod-template-hash=9456bbbf9
nginx-deployment-9456bbbf9-lnxq8   1/1     Running             0          77m   app=nginx,pod-template-hash=9456bbbf9
nginx-deployment-ff6655784-vx5fr   0/1     ContainerCreating   0          17s   app=nginx,pod-template-hash=ff6655784

[root@k8sm1 ~]# kubectl get pods -n app --show-labels       
NAME                               READY   STATUS              RESTARTS   AGE   LABELS
nginx-deployment-9456bbbf9-9pssm   1/1     Running             0          77m   app=nginx,pod-template-hash=9456bbbf9
nginx-deployment-9456bbbf9-gwlf8   1/1     Running             0          77m   app=nginx,pod-template-hash=9456bbbf9
nginx-deployment-ff6655784-vx5fr   1/1     Running             0          35s   app=nginx,pod-template-hash=ff6655784
nginx-deployment-ff6655784-wlvrk   0/1     ContainerCreating   0          10s   app=nginx,pod-template-hash=ff6655784

[root@k8sm1 ~]# kubectl get pods -n app --show-labels       
NAME                               READY   STATUS    RESTARTS   AGE   LABELS
nginx-deployment-ff6655784-qfz68   1/1     Running   0          4s    app=nginx,pod-template-hash=ff6655784
nginx-deployment-ff6655784-vx5fr   1/1     Running   0          51s   app=nginx,pod-template-hash=ff6655784
nginx-deployment-ff6655784-wlvrk   1/1     Running   0          26s   app=nginx,pod-template-hash=ff6655784

image-20230826105630697

查看 Deployment 运行信息:

[root@k8sm1 ~]# kubectl describe deployments nginx-deployment -n app
Name:                   nginx-deployment
Namespace:              app
CreationTimestamp:      Thu, 24 Aug 2023 19:48:31 +0800
Labels:                 app=nginx
Annotations:            deployment.kubernetes.io/revision: 2
Selector:               app=nginx
Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=nginx
  Containers:
   nginx:
    Image:        nginx:1.16.1
    Port:         80/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   nginx-deployment-ff6655784 (3/3 replicas created)
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  22m   deployment-controller  Scaled up replica set nginx-deployment-ff6655784 to 1
  Normal  ScalingReplicaSet  21m   deployment-controller  Scaled down replica set nginx-deployment-9456bbbf9 to 2
  Normal  ScalingReplicaSet  21m   deployment-controller  Scaled up replica set nginx-deployment-ff6655784 to 2
  Normal  ScalingReplicaSet  21m   deployment-controller  Scaled down replica set nginx-deployment-9456bbbf9 to 1
  Normal  ScalingReplicaSet  21m   deployment-controller  Scaled up replica set nginx-deployment-ff6655784 to 3
  Normal  ScalingReplicaSet  21m   deployment-controller  Scaled down replica set nginx-deployment-9456bbbf9 to 0

回滚

如果发现当前版本有问题,可以先回滚到之前的版本。

先查看 Deployment 历史:

[root@k8sm1 ~]# kubectl rollout history deployment/nginx-deployment -n app
deployment.apps/nginx-deployment 
REVISION  CHANGE-CAUSE
1         <none>
2         <none>

[root@k8sm1 ~]# kubectl rollout history deployment/nginx-deployment -n app --revision=1
deployment.apps/nginx-deployment with revision #1
Pod Template:
  Labels:       app=nginx
        pod-template-hash=9456bbbf9
  Containers:
   nginx:
    Image:      nginx:1.14.2
    Port:       80/TCP
    Host Port:  0/TCP
    Environment:        <none>
    Mounts:     <none>
  Volumes:      <none>

[root@k8sm1 ~]# kubectl rollout history deployment/nginx-deployment -n app --revision=2
deployment.apps/nginx-deployment with revision #2
Pod Template:
  Labels:       app=nginx
        pod-template-hash=ff6655784
  Containers:
   nginx:
    Image:      nginx:1.16.1
    Port:       80/TCP
    Host Port:  0/TCP
    Environment:        <none>
    Mounts:     <none>
  Volumes:      <none>

回滚到前一个版本:

[root@k8sm1 ~]# kubectl rollout undo deployment/nginx-deployment -n app
deployment.apps/nginx-deployment rolled back

查看:

[root@k8sm1 ~]# kubectl get all -n app
NAME                                   READY   STATUS    RESTARTS   AGE
pod/nginx-deployment-9456bbbf9-7wcrw   1/1     Running   0          16s
pod/nginx-deployment-9456bbbf9-rw9cx   1/1     Running   0          14s
pod/nginx-deployment-9456bbbf9-t6srk   1/1     Running   0          12s

NAME                               READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx-deployment   3/3     3            3           29m

NAME                                         DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-deployment-9456bbbf9   3         3         3       29m
replicaset.apps/nginx-deployment-ff6655784   0         0         0       28m

[root@k8sm1 ~]# kubectl describe deployment nginx-deployment -n app
Name:                   nginx-deployment
Namespace:              app
CreationTimestamp:      Fri, 25 Aug 2023 10:35:03 +0800
Labels:                 app=nginx
Annotations:            deployment.kubernetes.io/revision: 3
Selector:               app=nginx
Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=nginx
  Containers:
   nginx:
    Image:        nginx:1.14.2
    Port:         80/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   nginx-deployment-9456bbbf9 (3/3 replicas created)
Events:
  Type    Reason             Age                From                   Message
  ----    ------             ----               ----                   -------
  Normal  ScalingReplicaSet  30m                deployment-controller  Scaled up replica set nginx-deployment-ff6655784 to 1
  Normal  ScalingReplicaSet  28m                deployment-controller  Scaled down replica set nginx-deployment-9456bbbf9 to 2
  Normal  ScalingReplicaSet  28m                deployment-controller  Scaled up replica set nginx-deployment-ff6655784 to 2
  Normal  ScalingReplicaSet  25m                deployment-controller  Scaled down replica set nginx-deployment-9456bbbf9 to 1
  Normal  ScalingReplicaSet  25m                deployment-controller  Scaled up replica set nginx-deployment-ff6655784 to 3
  Normal  ScalingReplicaSet  25m                deployment-controller  Scaled down replica set nginx-deployment-9456bbbf9 to 0
  Normal  ScalingReplicaSet  102s               deployment-controller  Scaled up replica set nginx-deployment-9456bbbf9 to 1
  Normal  ScalingReplicaSet  100s               deployment-controller  Scaled down replica set nginx-deployment-ff6655784 to 2
  Normal  ScalingReplicaSet  100s               deployment-controller  Scaled up replica set nginx-deployment-9456bbbf9 to 2
  Normal  ScalingReplicaSet  98s (x2 over 31m)  deployment-controller  Scaled up replica set nginx-deployment-9456bbbf9 to 3
  Normal  ScalingReplicaSet  98s                deployment-controller  Scaled down replica set nginx-deployment-ff6655784 to 1
  Normal  ScalingReplicaSet  96s                deployment-controller  Scaled down replica set nginx-deployment-ff6655784 to 0

再次先查看 Deployment 历史:

[root@k8sm1 ~]# kubectl rollout history deployment/nginx-deployment -n app
deployment.apps/nginx-deployment 
REVISION  CHANGE-CAUSE
2         <none>
3         <none>

回滚到指定版本:

[root@k8sm1 ~]# kubectl rollout undo deployment/nginx-deployment --to-revision=2 -n app
deployment.apps/nginx-deployment rolled back

查看:

[root@k8sm1 ~]# kubectl get all -n app
NAME                                   READY   STATUS    RESTARTS   AGE
pod/nginx-deployment-ff6655784-h8qtv   1/1     Running   0          32s
pod/nginx-deployment-ff6655784-j7bj7   1/1     Running   0          36s
pod/nginx-deployment-ff6655784-kwsx7   1/1     Running   0          34s

NAME                               READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx-deployment   3/3     3            3           35m

NAME                                         DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-deployment-9456bbbf9   0         0         0       35m
replicaset.apps/nginx-deployment-ff6655784   3         3         3       34m

[root@k8sm1 ~]# kubectl describe deployment nginx-deployment -n app
Name:                   nginx-deployment
Namespace:              app
CreationTimestamp:      Fri, 25 Aug 2023 10:35:03 +0800
Labels:                 app=nginx
Annotations:            deployment.kubernetes.io/revision: 4
Selector:               app=nginx
Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=nginx
  Containers:
   nginx:
    Image:        nginx:1.16.1
    Port:         80/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   nginx-deployment-ff6655784 (3/3 replicas created)
Events:
  Type    Reason             Age                  From                   Message
  ----    ------             ----                 ----                   -------
  Normal  ScalingReplicaSet  30m                  deployment-controller  Scaled down replica set nginx-deployment-9456bbbf9 to 1
  Normal  ScalingReplicaSet  30m                  deployment-controller  Scaled up replica set nginx-deployment-ff6655784 to 3
  Normal  ScalingReplicaSet  30m                  deployment-controller  Scaled down replica set nginx-deployment-9456bbbf9 to 0
  Normal  ScalingReplicaSet  6m30s                deployment-controller  Scaled up replica set nginx-deployment-9456bbbf9 to 1
  Normal  ScalingReplicaSet  6m28s                deployment-controller  Scaled up replica set nginx-deployment-9456bbbf9 to 2
  Normal  ScalingReplicaSet  6m28s                deployment-controller  Scaled down replica set nginx-deployment-ff6655784 to 2
  Normal  ScalingReplicaSet  6m26s (x2 over 36m)  deployment-controller  Scaled up replica set nginx-deployment-9456bbbf9 to 3
  Normal  ScalingReplicaSet  6m26s                deployment-controller  Scaled down replica set nginx-deployment-ff6655784 to 1
  Normal  ScalingReplicaSet  6m24s                deployment-controller  Scaled down replica set nginx-deployment-ff6655784 to 0
  Normal  ScalingReplicaSet  55s (x2 over 34m)    deployment-controller  Scaled up replica set nginx-deployment-ff6655784 to 1
  Normal  ScalingReplicaSet  53s (x2 over 33m)    deployment-controller  Scaled up replica set nginx-deployment-ff6655784 to 2
  Normal  ScalingReplicaSet  53s (x2 over 33m)    deployment-controller  Scaled down replica set nginx-deployment-9456bbbf9 to 2
  Normal  ScalingReplicaSet  49s (x3 over 51s)    deployment-controller  (combined from similar events): Scaled down replica set nginx-deployment-9456bbbf9 to 0

扩缩容

扩容:

[root@k8sm1 ~]# kubectl scale deployment/nginx-deployment --replicas=4 -n app
deployment.apps/nginx-deployment scaled

[root@k8sm1 ~]# kubectl get all -n app
NAME                                   READY   STATUS    RESTARTS   AGE
pod/nginx-deployment-ff6655784-djxbf   1/1     Running   0          109s
pod/nginx-deployment-ff6655784-h8qtv   1/1     Running   0          21m
pod/nginx-deployment-ff6655784-j7bj7   1/1     Running   0          21m
pod/nginx-deployment-ff6655784-kwsx7   1/1     Running   0          21m

NAME                               READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx-deployment   4/4     4            4           56m

NAME                                         DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-deployment-9456bbbf9   0         0         0       56m
replicaset.apps/nginx-deployment-ff6655784   4         4         4       55m

缩容:

[root@k8sm1 ~]# kubectl scale deployment/nginx-deployment --replicas=2 -n app
deployment.apps/nginx-deployment scaled

[root@k8sm1 ~]# kubectl get all -n app
\NAME                                   READY   STATUS    RESTARTS   AGE
pod/nginx-deployment-ff6655784-j7bj7   1/1     Running   0          21m
pod/nginx-deployment-ff6655784-kwsx7   1/1     Running   0          21m

NAME                               READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx-deployment   2/2     2            2           56m

NAME                                         DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-deployment-9456bbbf9   0         0         0       56m
replicaset.apps/nginx-deployment-ff6655784   2         2         2       55m

Service

Service 是访问 Kubernetes 集群内部多个 Pod 的入口,可以理解为多个 Pod 的 VIP。

从前面创建的 Deployment 可知,在 Kubernetes 集群中,无状态的 Pod 副本是可以随时删除、随时创建的,并且重新创建的 Pod 不再保留旧 Pod 的任何信息,包括 IP 地址。在这种情况下,前端 Pod 如何使用后端 Pod 来提供服务就成了问题,因此 Kubernetes 实现了 Service 这样一个抽象概念。对于有 Selector 的 Service,在被创建的时候会自动创建 EndPoint 资源,包含了对应 Lable 的所有 Pod 的 IP 和端口,并且在之后的 Pod 的删除、创建中,也会立即更新相关 Pod 的 IP 和端口信息。同时,Service 的 IP 地址是永远固定的,Service 和 EndPoint 是一一对应的关系。这样,如果前端 Pod 通过固定的 Service IP 来访问后端 Pod 提供的服务,那么就可以在 EndPoint 中找到一个可用 Pod 的 IP 和端口,再将数据包转发到指定的 Pod 上即可。

image-20230828113739175

创建服务

使用如下资源配置清单文件 nginx-service.yaml 定义 Service:

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  namespace: app
  labels:
    app: nginx
spec:
  type: ClusterIP
  selector:
    app: nginx
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 80
  sessionAffinity: None

其中:

  • metadata.name:Service 名称
  • metadata.labels:Service 标签
  • spec.type:Service 类型,有以下几种:
    • ClusterIP:默认类型,通过集群的内部 IP 暴露服务,服务只能够从集群内部访问,其他类型的基础
    • NodePort:是 ClusterIP 类型的超集。使用 NAT 方式,通过每个节点上的 IP 和端口(默认 30000-32767)暴露服务,可以不指定由系统自动分配节点暴露的端口,也可以通过 spec.ports[*].nodePort 指定节点暴露的端口。前面创建 Dashboard 时就是采用的 NodePort 方式,直接将服务暴露到所有节点主机的端口上
    • LoadBalancer:用于支持云服务商提供的外部负载均衡器,是 NodePort 类型的超集
    • ExternalName:将服务映射到 DNS 名称,而不是 Selector
  • spec.selector:指定要访问的 Pod 的标签
  • spec.ports:指定端口信息,包括:
    • spec.ports[*].name:服务端口名称
    • spec.ports[*].protocol:端口协议
    • spec.ports[*].port:服务端口号
    • spec.ports[*].targetPort:对应的 Pod 端口,可以是端口号,也可以是创建 Pod 时指定的端口名称
  • spec.sessionAffinity:会话亲和性,默认为 None,可以配置为 ClientIP 将同一客户端会话连接到同一 Pod

创建 Service :

[root@k8sm1 ~]# kubectl apply -f nginx-service.yaml 
service/nginx-service created

查看 Service:

[root@k8sm1 ~]# kubectl get services -n app -o wide
NAME            TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE   SELECTOR
nginx-service   ClusterIP   10.97.154.99   <none>        80/TCP    52s   app=nginx

[root@k8sm1 ~]# kubectl describe services nginx-service  -n app
Name:              nginx-service
Namespace:         app
Labels:            app=nginx
Annotations:       <none>
Selector:          app=nginx
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.97.154.99
IPs:               10.97.154.99
Port:              http  80/TCP
TargetPort:        80/TCP
Endpoints:         10.244.1.20:80,10.244.2.19:80,10.244.2.20:80
Session Affinity:  None
Events:            <none>

可以看到该 Service 对接的 Endpoints 为 10.244.1.20:80,10.244.2.19:80,10.244.2.20:80

查看 Endpoint:

[root@k8sm1 ~]# kubectl get endpoints nginx-service -n app -o yaml
apiVersion: v1
kind: Endpoints
metadata:
  annotations:
    endpoints.kubernetes.io/last-change-trigger-time: "2023-08-28T02:33:38Z"
  creationTimestamp: "2023-08-28T02:33:38Z"
  labels:
    app: nginx
  name: nginx-service
  namespace: app
  resourceVersion: "198840"
  uid: 04d89ad4-c0be-4107-8595-847b56e50072
subsets:
- addresses:
  - ip: 10.244.1.20
    nodeName: k8sn1.stonecoding.net
    targetRef:
      kind: Pod
      name: nginx-deployment-9456bbbf9-b5b79
      namespace: app
      resourceVersion: "193892"
      uid: 15681a8a-7a57-48bc-b7c3-f569a3be0ccd
  - ip: 10.244.2.19
    nodeName: k8sn2.stonecoding.net
    targetRef:
      kind: Pod
      name: nginx-deployment-9456bbbf9-f4dxq
      namespace: app
      resourceVersion: "193991"
      uid: 384b52ec-1c49-4c68-b76a-572b0560ec29
  - ip: 10.244.2.20
    nodeName: k8sn2.stonecoding.net
    targetRef:
      kind: Pod
      name: nginx-deployment-9456bbbf9-j95b6
      namespace: app
      resourceVersion: "193988"
      uid: e9be4521-9f4a-4e09-9d63-2b5dd6857a75
  ports:
  - name: http
    port: 80
    protocol: TCP

将 Deployment 扩容为 4 副本:

[root@k8sm1 ~]# kubectl scale deployment/nginx-deployment --replicas=4 -n app
deployment.apps/nginx-deployment scaled

查看 Endpoint,可以看到将新增的 Pod 加入到 Endpoint 中:

[root@k8sm1 ~]# kubectl get endpoints nginx-service -n app -o yaml
apiVersion: v1
kind: Endpoints
metadata:
  annotations:
    endpoints.kubernetes.io/last-change-trigger-time: "2023-08-28T05:26:53Z"
  creationTimestamp: "2023-08-28T02:33:38Z"
  labels:
    app: nginx
  name: nginx-service
  namespace: app
  resourceVersion: "214715"
  uid: 04d89ad4-c0be-4107-8595-847b56e50072
subsets:
- addresses:
  - ip: 10.244.1.20
    nodeName: k8sn1.stonecoding.net
    targetRef:
      kind: Pod
      name: nginx-deployment-9456bbbf9-b5b79
      namespace: app
      resourceVersion: "193892"
      uid: 15681a8a-7a57-48bc-b7c3-f569a3be0ccd
  - ip: 10.244.1.21
    nodeName: k8sn1.stonecoding.net
    targetRef:
      kind: Pod
      name: nginx-deployment-9456bbbf9-6hn6v
      namespace: app
      resourceVersion: "214711"
      uid: b833c8ad-42bf-4f1e-bb77-6ba5a0c39e95
  - ip: 10.244.2.19
    nodeName: k8sn2.stonecoding.net
    targetRef:
      kind: Pod
      name: nginx-deployment-9456bbbf9-f4dxq
      namespace: app
      resourceVersion: "193991"
      uid: 384b52ec-1c49-4c68-b76a-572b0560ec29
  - ip: 10.244.2.20
    nodeName: k8sn2.stonecoding.net
    targetRef:
      kind: Pod
      name: nginx-deployment-9456bbbf9-j95b6
      namespace: app
      resourceVersion: "193988"
      uid: e9be4521-9f4a-4e09-9d63-2b5dd6857a75
  ports:
  - name: http
    port: 80
    protocol: TCP

将 Deployment 缩容为 3 副本:

[root@k8sm1 ~]# kubectl scale deployment/nginx-deployment --replicas=2 -n app
deployment.apps/nginx-deployment scaled

查看 Endpoint,就只有剩下的 Pod 了:

[root@k8sm1 ~]# kubectl get endpoints nginx-service -n app -o yaml
apiVersion: v1
kind: Endpoints
metadata:
  creationTimestamp: "2023-08-28T02:33:38Z"
  labels:
    app: nginx
  name: nginx-service
  namespace: app
  resourceVersion: "215281"
  uid: 04d89ad4-c0be-4107-8595-847b56e50072
subsets:
- addresses:
  - ip: 10.244.1.20
    nodeName: k8sn1.stonecoding.net
    targetRef:
      kind: Pod
      name: nginx-deployment-9456bbbf9-b5b79
      namespace: app
      resourceVersion: "193892"
      uid: 15681a8a-7a57-48bc-b7c3-f569a3be0ccd
  - ip: 10.244.2.19
    nodeName: k8sn2.stonecoding.net
    targetRef:
      kind: Pod
      name: nginx-deployment-9456bbbf9-f4dxq
      namespace: app
      resourceVersion: "193991"
      uid: 384b52ec-1c49-4c68-b76a-572b0560ec29
  ports:
  - name: http
    port: 80
    protocol: TCP

服务发现

对于集群内的 Pod,Kubernetes 支持两种服务发现模式:环境变量和 DNS。

环境变量

创建 Service 之后创建的 Pod 会被注入之前 Service 对应的环境变量,格式为 {SVCNAME}_SERVICE_HOST{SVCNAME}_SERVICE_PORT(将服务名称转换为大小,横线转换为下划线),这样 Pod 就可以通过其环境变量访问对应的服务了。

创建一个 Service 及其对应的一个 Nginx 镜像版本为 1.18 的 Deployment:

[root@k8sm1 ~]# vi nginx-v118.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-service-v118
  namespace: app
  labels:
    app: nginx-v118
spec:
  type: ClusterIP
  selector:
    app: nginx-v118
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 80
  sessionAffinity: None

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment-v118
  namespace: app
  labels:
    app: nginx-v118
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-v118
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: nginx-v118
    spec:
      containers:
      - name: nginx
        image: nginx:1.18
        ports:
        - containerPort: 80
        
[root@k8sm1 ~]# kubectl apply -f nginx-v118.yaml 
service/nginx-service-v118 created
deployment.apps/nginx-deployment-v118 created

[root@k8sm1 ~]# kubectl get all -n app
NAME                                         READY   STATUS    RESTARTS   AGE
pod/nginx-deployment-9456bbbf9-5cdgr         1/1     Running   0          8m3s
pod/nginx-deployment-9456bbbf9-87rfh         1/1     Running   0          8m
pod/nginx-deployment-9456bbbf9-jf8zw         1/1     Running   0          7m59s
pod/nginx-deployment-v118-69584f9576-d4cw6   1/1     Running   0          15s

NAME                         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/nginx-service        ClusterIP   10.97.154.99    <none>        80/TCP    3h52m
service/nginx-service-v118   ClusterIP   10.111.251.40   <none>        80/TCP    15s

NAME                                    READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx-deployment        3/3     3            3           3d
deployment.apps/nginx-deployment-v118   1/1     1            1           15s

NAME                                               DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-deployment-6dfc66cc6c        0         0         0       10m
replicaset.apps/nginx-deployment-9456bbbf9         3         3         3       3d
replicaset.apps/nginx-deployment-v118-69584f9576   1         1         1       15s

进入 Pod nginx-deployment-v118-69584f9576-d4cw6 查看环境变量:

[root@k8sm1 ~]# kubectl exec -it nginx-deployment-v118-69584f9576-d4cw6 sh -n app
# env | grep NGINX_SERVICE_V118
NGINX_SERVICE_V118_SERVICE_HOST=10.111.251.40
NGINX_SERVICE_V118_PORT=tcp://10.111.251.40:80
NGINX_SERVICE_V118_SERVICE_PORT=80
NGINX_SERVICE_V118_PORT_80_TCP_ADDR=10.111.251.40
NGINX_SERVICE_V118_PORT_80_TCP_PORT=80
NGINX_SERVICE_V118_PORT_80_TCP_PROTO=tcp
NGINX_SERVICE_V118_PORT_80_TCP=tcp://10.111.251.40:80
NGINX_SERVICE_V118_SERVICE_PORT_HTTP=80
DNS

使用环境变量作为服务发现必须先创建服务,再创建 Pod。更常用的是使用组件 CoreDNS 提供的 DNS 服务来进行服务发现。

[root@k8sm1 ~]# kubectl get pods -n kube-system -o wide | grep dns
coredns-6d8c4cb4d-bwwxw                   1/1     Running   5 (5h31m ago)   7d3h   10.244.0.13      k8sm1.stonecoding.net   <none>           <none>
coredns-6d8c4cb4d-zp7nr                   1/1     Running   5 (5h31m ago)   7d3h   10.244.0.12      k8sm1.stonecoding.net   <none>           <none>

[root@k8sm1 ~]# kubectl get services -n kube-system
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
kube-dns   ClusterIP   10.96.0.10   <none>        53/UDP,53/TCP,9153/TCP   7d3h

[root@k8sm1 ~]# kubectl describe services kube-dns -n kube-system
Name:              kube-dns
Namespace:         kube-system
Labels:            k8s-app=kube-dns
                   kubernetes.io/cluster-service=true
                   kubernetes.io/name=CoreDNS
Annotations:       prometheus.io/port: 9153
                   prometheus.io/scrape: true
Selector:          k8s-app=kube-dns
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.96.0.10
IPs:               10.96.0.10
Port:              dns  53/UDP
TargetPort:        53/UDP
Endpoints:         10.244.0.12:53,10.244.0.13:53
Port:              dns-tcp  53/TCP
TargetPort:        53/TCP
Endpoints:         10.244.0.12:53,10.244.0.13:53
Port:              metrics  9153/TCP
TargetPort:        9153/TCP
Endpoints:         10.244.0.12:9153,10.244.0.13:9153
Session Affinity:  None
Events:            <none>

创建一个镜像为 busybox 的 Pod:

[root@k8sm1 ~]# vi busybox-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: busybox
  namespace: default
spec:
  containers:
  - name: busybox
    image: busybox
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
  restartPolicy: Always
        
[root@k8sm1 ~]# kubectl apply -f busybox-pod.yaml 
pod/busybox created

[root@k8sm1 ~]# kubectl get pods
NAME      READY   STATUS    RESTARTS   AGE
busybox   1/1     Running   0          24s

进入容器查看服务:

[root@k8sm1 ~]# kubectl exec -it busybox -- /bin/sh
/ # nslookup 10.111.251.40
Server:         10.96.0.10
Address:        10.96.0.10:53

40.251.111.10.in-addr.arpa      name = nginx-service-v118.app.svc.cluster.local

服务代理

Kubernetes 集群的每个节点都有 kube-proxy 组件,作为服务代理,为服务实现 Virtual IP(VIP),使发往 Service 的流量负载均衡到正确的后端 Pod。

kube-proxy 监听 API Server 中资源对象的变化情况,包括以下三种:

  • Service
  • Endpoint/EndpointSlices(如果使用了 EndpointSlice,则会监听 EndpointSlice,否则监听 Endpoint)
  • Node

然后根据资源变化情况,为服务配置负载均衡。

在 Linux 上目前 Kube-proxy 可用模式有:

  • iptables
  • ipvs
iptables

Kube-proxy 默认使用 iptables 模式:

[root@k8sm1 ~]# curl http://localhost:10249/proxyMode
iptables

在这种模式下,Kube-proxy 监视 Kubernetes 控制平面,获知对 Service 和 EndpointSlice 的添加和删除操作。 对于每个 Service,Kube-proxy 会添加 iptables 规则,这些规则捕获流向 Service 的 clusterIPport 的流量, 并将这些流量重定向到 Service 后端 Pod 中的其中之一。对于每个 Endpoint,会添加指向某个后端 Pod 的 iptables 规则。

默认情况下,iptables 模式下的 Kube-proxy 会随机选择一个后端 Pod。

使用 iptables 处理流量的系统开销较低,因为流量由 Linux netfilter 处理,无需在用户空间和内核空间之间切换,也更为可靠。

如果 Kube-proxy 以 iptables 模式运行,并且选择的第一个 Pod 没有响应,那么连接会失败。可以使用 Pod 的就绪探针来验证后端 Pod 是否健康,以避免 Kube-proxy 将流量发送到失败的 Pod 上。

当创建 Service 时,Kubernetes 控制平面会分配一个虚拟 IP 地址,例如 10.97.154.99。当节点上的 Kube-proxy 观察到新 Service 时,会添加一系列 iptables 规则,以便将访问这个地址的流量转到真实的 Pod 上。

[root@k8sm1 ~]# iptables -t nat -nL OUTPUT
Chain OUTPUT (policy ACCEPT)
target     		prot opt source               destination         
KUBE-SERVICES  	all   --  0.0.0.0/0            0.0.0.0/0            /* kubernetes service portals */
DOCKER     		all   --  0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

第一条规则会匹配所有的流量,然后跳到 KUBE-SERVICES 这条链上。看一下 KUBE-SERVICES 的具体内容:

[root@k8sm1 ~]# iptables -t nat -nL KUBE-SERVICES
Chain KUBE-SERVICES (2 references)
target     				   prot opt source               destination         
KUBE-SVC-EDNDUDH2C75GIR6O  tcp  --  0.0.0.0/0            10.98.51.149         /* ingress-nginx/ingress-nginx-controller:https cluster IP */ tcp dpt:443
KUBE-SVC-EZYNCFY2F7N6OQA2  tcp  --  0.0.0.0/0            10.101.134.29        /* ingress-nginx/ingress-nginx-controller-admission:https-webhook cluster IP */ tcp dpt:443
KUBE-SVC-CEZPIJSAUFW5MYPQ  tcp  --  0.0.0.0/0            10.107.3.191         /* kubernetes-dashboard/kubernetes-dashboard cluster IP */ tcp dpt:443
KUBE-SVC-CB4FKN7PLFL7IROR  tcp  --  0.0.0.0/0            10.97.154.99         /* app/nginx-service:http cluster IP */ tcp dpt:80
KUBE-SVC-NPX46M4PTMTKRN6Y  tcp  --  0.0.0.0/0            10.96.0.1            /* default/kubernetes:https cluster IP */ tcp dpt:443
KUBE-SVC-CG5I4G2RS3ZVWGLK  tcp  --  0.0.0.0/0            10.98.51.149         /* ingress-nginx/ingress-nginx-controller:http cluster IP */ tcp dpt:80
KUBE-SVC-JD5MR3NA4I4DYORP  tcp  --  0.0.0.0/0            10.96.0.10           /* kube-system/kube-dns:metrics cluster IP */ tcp dpt:9153
KUBE-SVC-TCOU7JCQXEZGVUNU  udp  --  0.0.0.0/0            10.96.0.10           /* kube-system/kube-dns:dns cluster IP */ udp dpt:53
KUBE-SVC-ERIFXISQEP7F7OF4  tcp  --  0.0.0.0/0            10.96.0.10           /* kube-system/kube-dns:dns-tcp cluster IP */ tcp dpt:53
KUBE-SVC-Z6GDYMWE5TV2NNJN  tcp  --  0.0.0.0/0            10.110.86.109        /* kubernetes-dashboard/dashboard-metrics-scraper cluster IP */ tcp dpt:8000
KUBE-SVC-BMEYHMRRLQBXSPQR  tcp  --  0.0.0.0/0            10.111.251.40        /* app/nginx-service-v118:http cluster IP */ tcp dpt:80
KUBE-NODEPORTS  		   all  --  0.0.0.0/0            0.0.0.0/0            /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL

这里前面的 KUBE-SVC-* 都是根据 destinationprotocol 和目的端口号来匹配的,根据 Service 地址和端口号以及协议,可以匹配到 KUBE-SVC-CB4FKN7PLFL7IROR 这条规则,然后跳到这条链上。接着看这条链的定义:

[root@k8sm1 ~]# iptables -t nat -nL KUBE-SVC-CB4FKN7PLFL7IROR
Chain KUBE-SVC-CB4FKN7PLFL7IROR (1 references)
target     				   prot opt source               destination         
KUBE-MARK-MASQ  		   tcp  -- !10.244.0.0/16        10.97.154.99         /* app/nginx-service:http cluster IP */ tcp dpt:80
KUBE-SEP-6IWAUY3HDSBJG6JK  all  --  0.0.0.0/0            0.0.0.0/0            /* app/nginx-service:http */ statistic mode random probability 0.33333333349
KUBE-SEP-6SVYFH2G76OTHT2L  all  --  0.0.0.0/0            0.0.0.0/0            /* app/nginx-service:http */ statistic mode random probability 0.50000000000
KUBE-SEP-L2XRLFTEOGVDOKM7  all  --  0.0.0.0/0            0.0.0.0/0            /* app/nginx-service:http */

最下面的 2-4 条规则就是使用 iptables 实现负载均衡的地方了。第 2 条规则有 33% 的匹配几率。如果匹配到了其中一条,就会跳到这个链上。比如:

[root@k8sm1 ~]# iptables -t nat -nL KUBE-SEP-6IWAUY3HDSBJG6JK
Chain KUBE-SEP-6IWAUY3HDSBJG6JK (1 references)
target     		prot opt source               destination         
KUBE-MARK-MASQ  all  --  10.244.1.25          0.0.0.0/0            /* app/nginx-service:http */
DNAT      		tcp  --  0.0.0.0/0            0.0.0.0/0            /* app/nginx-service:http */ tcp to:10.244.1.25:80

第 2 条规则将目的 IP 和 Port 改写成 10.244.1.25:80,也就是 Pod 的 IP 和 Port,这样流量就经过负载均衡指向了后端的 Pod。

IPVS

在 IPVS 模式下,Kube-proxy 监视 Kubernetes Service 和 EndpointSlice,然后调用 netlink 接口创建 IPVS 规则, 并定期与 Kubernetes Service 和 EndpointSlice 同步 IPVS 规则,以确保 IPVS 状态与期望的状态保持一致。 访问 Service 时,IPVS 会将流量导向到某个后端 Pod。

IPVS 代理模式基于 netfilter 回调函数,类似于 iptables 模式, 但使用哈希表作为底层数据结构,工作在内核空间。 这意味着 IPVS 模式下的 Kube-proxy 比 iptables 模式下的 Kube-proxy 重定向流量的延迟更低,同步代理规则时性能也更好。 当服务数量超过 10000 个时,建议使用 IPVS。

IPVS 可以使用的负载均衡算法有:

  • rr:轮询
  • lc:最少连接(打开连接数最少)
  • dh:目标地址哈希
  • sh:源地址哈希
  • sed:最短预期延迟
  • nq:最少队列

注意: 要在 IPVS 模式下运行 Kube-proxy,必须在启动 Kube-proxy 之前确保节点上的 IPVS 可用。

当 Kube-proxy 以 IPVS 代理模式启动时,将验证 IPVS 内核模块是否可用。 如果未检测到 IPVS 内核模块,则 Kube-proxy 将退回到以 iptables 代理模式运行。

Ingress

可以使用 Service 的 NodePort 将服务暴露在宿主机的端口上供外部访问,但随着服务的增多,会大量占用宿主机的端口,维护大量的 NodePort 也非常麻烦。此时就需要使用 Ingress 来作为反向代理(类似于 Nginx)处理外部对集群服务的 HTTP 请求。

Ingress 是 Kubernetes 中的一个资源对象,作为集群内服务对外暴露的统一入口,将外部流量路由到 Kubernetes 集群内的服务。可以通过 Ingress 资源来配置不同的转发规则,从而达到根据不同的规则设置访问集群内不同的 Service 所对应的后端 Pod。

image-20230829140701036

为了使 Ingress 正常工作,集群中必须运行 Ingress 控制器,Ingress 控制器有很多种,这里使用前面已经安装好的 Ingress-Nginx。

[root@k8sm1 ~]# kubectl get all -n ingress-nginx
NAME                                       READY   STATUS      RESTARTS   AGE
pod/ingress-nginx-admission-create-xltbp   0/1     Completed   0          127m
pod/ingress-nginx-admission-patch-gh9p5    0/1     Completed   0          127m
pod/ingress-nginx-controller-j49qh         1/1     Running     0          127m
pod/ingress-nginx-controller-whd5f         1/1     Running     0          127m

NAME                                         TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
service/ingress-nginx-controller             LoadBalancer   10.99.217.210   <pending>     80:31412/TCP,443:30818/TCP   127m
service/ingress-nginx-controller-admission   ClusterIP      10.109.220.22   <none>        443/TCP                      127m

NAME                                      DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR                          AGE
daemonset.apps/ingress-nginx-controller   2         2         2       2            2           ingress=nginx,kubernetes.io/os=linux   127m

NAME                                       COMPLETIONS   DURATION   AGE
job.batch/ingress-nginx-admission-create   1/1           4s         127m
job.batch/ingress-nginx-admission-patch    1/1           5s         127m

[root@k8sm1 ~]# kubectl get ingressclass
NAME    CONTROLLER             PARAMETERS   AGE
nginx   k8s.io/ingress-nginx   <none>       167m

其中 Pod 有:

  • ingress-nginx-controller 是 Ingress-Nginx 的控制器组件,负责监视 Kubernetes API Server 上的 Ingress 对象,并根据配置动态地更新 Nginx 配置文件,实现 HTTP(S) 的负载均衡和路由。
  • ingress-nginx-admission-create 是一个 Kubernetes Admission Controller,可以拦截 Kubernetes 集群内的 Ingress 对象的创建操作,并在 Ingress 对象创建之前,对其进行一些额外的验证和处理。
  • ingress-nginx-admission-patch 也是一个 Kubernetes Admission Controller,可以在 Ingress 对象更新之前,对其进行额外的验证和处理,类似于 ingress-nginx-admission-create

Service 有:

  • ingress-nginx-controller 负责将请求转发到 ingress-nginx-controller Pods。它通常会将流量分发到 ingress-nginx-controller 的多个副本中,并确保副本集的负载平衡。这个 Service 可以被配置为使用 NodePortLoadBalancerClusterIP 类型,根据需要进行暴露。
  • ingress-nginx-controller-admission 用于 Kubernetes Admission Webhooks,允许在创建、更新或删除资源时,对其进行校验或修改。它提供了一个 API Endpoint,用于与 Kubernetes API Server 进行通信,以便进行这些校验或修改。这个 Service 可以被配置为使用 NodePortLoadBalancerClusterIP 类型,根据需要进行暴露。

创建

使用如下资源配置清单文件 web-ingress.yaml 定义 Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
  namespace: app
spec:
  ingressClassName: nginx
  rules:
  - host: web.stonecoding.net
    http:
      paths:
        - path: /
          pathType: Prefix
          backend:
            service:
              name: nginx-service
              port:
                number: 80

其中:

  • ingressClassName:指定使用的 Ingress 控制器

  • spec.rules[*].http.paths[*].pathType:有 3 种路径类型:

    • ImplementationSpecific:匹配方法取决于 IngressClass
    • Exact:精确匹配 URL 路径,且区分大小写
    • Prefix:基于以 / 分隔的 URL 路径前缀匹配,区分大小写

匹配示例如下:

KindPath(s)Request path(s)Matches?
Prefix/(all paths)Yes
Exact/foo/fooYes
Exact/foo/barNo
Exact/foo/foo/No
Exact/foo//fooNo
Prefix/foo/foo, /foo/Yes
Prefix/foo//foo, /foo/Yes
Prefix/aaa/bb/aaa/bbbNo
Prefix/aaa/bbb/aaa/bbbYes
Prefix/aaa/bbb//aaa/bbbYes, ignores trailing slash
Prefix/aaa/bbb/aaa/bbb/Yes, matches trailing slash
Prefix/aaa/bbb/aaa/bbb/cccYes, matches subpath
Prefix/aaa/bbb/aaa/bbbxyzNo, does not match string prefix
Prefix/, /aaa/aaa/cccYes, matches /aaa prefix
Prefix/, /aaa, /aaa/bbb/aaa/bbbYes, matches /aaa/bbb prefix
Prefix/, /aaa, /aaa/bbb/cccYes, matches / prefix
Prefix/aaa/cccNo, uses default backend
Mixed/foo (Prefix), /foo (Exact)/fooYes, prefers Exact

创建 Ingress:

[root@k8sm1 ~]# kubectl apply -f web-ingress.yaml 
ingress.networking.k8s.io/web created

查看 Ingress:

[root@k8sm1 ~]# kubectl get ingress -n app
NAME   CLASS   HOSTS           ADDRESS   PORTS   AGE
web    nginx   web.stonecoding.net             80      22m

[root@k8sm1 ~]# kubectl describe ingress web -n app
Name:             web
Labels:           <none>
Namespace:        app
Address:          
Default backend:  default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
Rules:
  Host           Path  Backends
  ----           ----  --------
  web.stonecoding.net  
                 /   nginx-service:80 (10.244.1.30:80,10.244.1.31:80,10.244.2.32:80)
Annotations:     nginx.ingress.kubernetes.io/rewrite-target: /$1
Events:
  Type    Reason  Age   From                      Message
  ----    ------  ----  ----                      -------
  Normal  Sync    50m   nginx-ingress-controller  Scheduled for sync
  Normal  Sync    50m   nginx-ingress-controller  Scheduled for sync

出现 error: endpoints "default-http-backend" not found 报错,需要创建服务 default-http-backend,资源配置清单文件如下:

apiVersion: v1
kind: Service
metadata:
  name: default-http-backend
  namespace: kube-system
spec:
  selector:
    app: ingress-nginx-controller
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

创建服务:

[root@k8sm1 ~]# kubectl apply -f default-http-backend-service.yaml 
service/default-http-backend created

再次查看 Ingress:

[root@k8sm1 ~]# kubectl describe ingress web -n app
Name:             web
Labels:           <none>
Namespace:        app
Address:          
Default backend:  default-http-backend:80 (<none>)
Rules:
  Host           Path  Backends
  ----           ----  --------
  web.stonecoding.net  
                 /   nginx-service:80 (10.244.1.30:80,10.244.1.31:80,10.244.2.32:80)
Annotations:     nginx.ingress.kubernetes.io/rewrite-target: /$1
Events:
  Type    Reason  Age    From                      Message
  ----    ------  ----   ----                      -------
  Normal  Sync    5m44s  nginx-ingress-controller  Scheduled for sync
  Normal  Sync    5m44s  nginx-ingress-controller  Scheduled for sync

配置好 web.stonecoding.net 域名解析,指向地址 192.168.92.140,即可使用该域名进行访问:

image-20230830160529202

Node

Node 是 Kubernetes 集群的物理节点。

查看所有节点信息:

[root@k8sm1 ~]# kubectl get nodes -o wide
NAME              STATUS   ROLES                  AGE   VERSION    INTERNAL-IP      EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION           CONTAINER-RUNTIME
k8sm1.stonecoding.net   Ready    control-plane,master   23h   v1.23.17   192.168.92.140   <none>        CentOS Linux 7 (Core)   3.10.0-1160.el7.x86_64   docker://20.10.23
k8sn1.stonecoding.net   Ready    <none>                 23h   v1.23.17   192.168.92.141   <none>        CentOS Linux 7 (Core)   3.10.0-1160.el7.x86_64   docker://20.10.23
k8sn2.stonecoding.net   Ready    <none>                 23h   v1.23.17   192.168.92.142   <none>        CentOS Linux 7 (Core)   3.10.0-1160.el7.x86_64   docker://20.10.23

查看指定节点信息:

[root@k8sm1 ~]# kubectl describe node k8sm1
Name:               k8sm1.stonecoding.net
Roles:              control-plane,master
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/os=linux
                    kubernetes.io/arch=amd64
                    kubernetes.io/hostname=k8sm1.stonecoding.net
                    kubernetes.io/os=linux
                    node-role.kubernetes.io/control-plane=
                    node-role.kubernetes.io/master=
                    node.kubernetes.io/exclude-from-external-load-balancers=
Annotations:        flannel.alpha.coreos.com/backend-data: {"VNI":1,"VtepMAC":"fa:9d:c6:a2:46:3a"}
                    flannel.alpha.coreos.com/backend-type: vxlan
                    flannel.alpha.coreos.com/kube-subnet-manager: true
                    flannel.alpha.coreos.com/public-ip: 192.168.92.140
                    kubeadm.alpha.kubernetes.io/cri-socket: /var/run/dockershim.sock
                    node.alpha.kubernetes.io/ttl: 0
                    volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp:  Mon, 21 Aug 2023 11:28:29 +0800
Taints:             node-role.kubernetes.io/master:NoSchedule
Unschedulable:      false
Lease:
  HolderIdentity:  k8sm1.stonecoding.net
  AcquireTime:     <unset>
  RenewTime:       Wed, 23 Aug 2023 14:39:40 +0800
Conditions:
  Type                 Status  LastHeartbeatTime                 LastTransitionTime                Reason                       Message
  ----                 ------  -----------------                 ------------------                ------                       -------
  NetworkUnavailable   False   Wed, 23 Aug 2023 08:51:27 +0800   Wed, 23 Aug 2023 08:51:27 +0800   FlannelIsUp                  Flannel is running on this node
  MemoryPressure       False   Wed, 23 Aug 2023 14:36:58 +0800   Mon, 21 Aug 2023 11:28:20 +0800   KubeletHasSufficientMemory   kubelet has sufficient memory available
  DiskPressure         False   Wed, 23 Aug 2023 14:36:58 +0800   Mon, 21 Aug 2023 11:28:20 +0800   KubeletHasNoDiskPressure     kubelet has no disk pressure
  PIDPressure          False   Wed, 23 Aug 2023 14:36:58 +0800   Mon, 21 Aug 2023 11:28:20 +0800   KubeletHasSufficientPID      kubelet has sufficient PID available
  Ready                True    Wed, 23 Aug 2023 14:36:58 +0800   Mon, 21 Aug 2023 15:27:04 +0800   KubeletReady                 kubelet is posting ready status
Addresses:
  InternalIP:  192.168.92.140
  Hostname:    k8sm1.stonecoding.net
Capacity:
  cpu:                2
  ephemeral-storage:  17394Mi
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             3861292Ki
  pods:               110
Allocatable:
  cpu:                2
  ephemeral-storage:  16415037823
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             3758892Ki
  pods:               110
System Info:
  Machine ID:                 25ffa45cb6834fb0a7f70f8567485af8
  System UUID:                564D0B5B-3F3A-3F0C-7016-A769F6FA5BE5
  Boot ID:                    c11c3bf2-62da-41be-a6a3-4b5c5c2b75af
  Kernel Version:             3.10.0-1160.el7.x86_64
  OS Image:                   CentOS Linux 7 (Core)
  Operating System:           linux
  Architecture:               amd64
  Container Runtime Version:  docker://20.10.23
  Kubelet Version:            v1.23.17
  Kube-Proxy Version:         v1.23.17
PodCIDR:                      10.244.0.0/24
PodCIDRs:                     10.244.0.0/24
Non-terminated Pods:          (8 in total)
  Namespace                   Name                                       CPU Requests  CPU Limits  Memory Requests  Memory Limits  Age
  ---------                   ----                                       ------------  ----------  ---------------  -------------  ---
  kube-flannel                kube-flannel-ds-h25mg                      100m (5%)     0 (0%)      50Mi (1%)        0 (0%)         2d3h
  kube-system                 coredns-6d8c4cb4d-bwwxw                    100m (5%)     0 (0%)      70Mi (1%)        170Mi (4%)     2d3h
  kube-system                 coredns-6d8c4cb4d-zp7nr                    100m (5%)     0 (0%)      70Mi (1%)        170Mi (4%)     2d3h
  kube-system                 etcd-k8sm1.stonecoding.net                       100m (5%)     0 (0%)      100Mi (2%)       0 (0%)         2d3h
  kube-system                 kube-apiserver-k8sm1.stonecoding.net             250m (12%)    0 (0%)      0 (0%)           0 (0%)         2d3h
  kube-system                 kube-controller-manager-k8sm1.stonecoding.net    200m (10%)    0 (0%)      0 (0%)           0 (0%)         2d3h
  kube-system                 kube-proxy-4dl9s                           0 (0%)        0 (0%)      0 (0%)           0 (0%)         2d3h
  kube-system                 kube-scheduler-k8sm1.stonecoding.net             100m (5%)     0 (0%)      0 (0%)           0 (0%)         2d3h
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource           Requests    Limits
  --------           --------    ------
  cpu                950m (47%)  0 (0%)
  memory             290Mi (7%)  340Mi (9%)
  ephemeral-storage  0 (0%)      0 (0%)
  hugepages-1Gi      0 (0%)      0 (0%)
  hugepages-2Mi      0 (0%)      0 (0%)
Events:              <none>

或者:

[root@k8sm1 ~]# kubectl get nodes k8sm1.stonecoding.net -o yaml
apiVersion: v1
kind: Node
metadata:
  annotations:
    flannel.alpha.coreos.com/backend-data: '{"VNI":1,"VtepMAC":"ca:f3:8a:05:77:ab"}'
    flannel.alpha.coreos.com/backend-type: vxlan
    flannel.alpha.coreos.com/kube-subnet-manager: "true"
    flannel.alpha.coreos.com/public-ip: 192.168.92.140
    kubeadm.alpha.kubernetes.io/cri-socket: /var/run/dockershim.sock
    node.alpha.kubernetes.io/ttl: "0"
    volumes.kubernetes.io/controller-managed-attach-detach: "true"
  creationTimestamp: "2023-08-21T03:28:29Z"
  labels:
    beta.kubernetes.io/arch: amd64
    beta.kubernetes.io/os: linux
    kubernetes.io/arch: amd64
    kubernetes.io/hostname: k8sm1.stonecoding.net
    kubernetes.io/os: linux
    node-role.kubernetes.io/control-plane: ""
    node-role.kubernetes.io/master: ""
    node.kubernetes.io/exclude-from-external-load-balancers: ""
  name: k8sm1.stonecoding.net
  resourceVersion: "45356"
  uid: c013d0a0-08cd-4afe-abca-3640079df59f
spec:
  podCIDR: 10.244.0.0/24
  podCIDRs:
  - 10.244.0.0/24
  taints:
  - effect: NoSchedule
    key: node-role.kubernetes.io/master
status:
  addresses:
  - address: 192.168.92.140
    type: InternalIP
  - address: k8sm1.stonecoding.net
    type: Hostname
  allocatable:
    cpu: "2"
    ephemeral-storage: "16415037823"
    hugepages-1Gi: "0"
    hugepages-2Mi: "0"
    memory: 3758900Ki
    pods: "110"
  capacity:
    cpu: "2"
    ephemeral-storage: 17394Mi
    hugepages-1Gi: "0"
    hugepages-2Mi: "0"
    memory: 3861300Ki
    pods: "110"
  conditions:
  - lastHeartbeatTime: "2023-08-22T01:05:38Z"
    lastTransitionTime: "2023-08-22T01:05:38Z"
    message: Flannel is running on this node
    reason: FlannelIsUp
    status: "False"
    type: NetworkUnavailable
  - lastHeartbeatTime: "2023-08-22T03:44:04Z"
    lastTransitionTime: "2023-08-21T03:28:20Z"
    message: kubelet has sufficient memory available
    reason: KubeletHasSufficientMemory
    status: "False"
    type: MemoryPressure
  - lastHeartbeatTime: "2023-08-22T03:44:04Z"
    lastTransitionTime: "2023-08-21T03:28:20Z"
    message: kubelet has no disk pressure
    reason: KubeletHasNoDiskPressure
    status: "False"
    type: DiskPressure
  - lastHeartbeatTime: "2023-08-22T03:44:04Z"
    lastTransitionTime: "2023-08-21T03:28:20Z"
    message: kubelet has sufficient PID available
    reason: KubeletHasSufficientPID
    status: "False"
    type: PIDPressure
  - lastHeartbeatTime: "2023-08-22T03:44:04Z"
    lastTransitionTime: "2023-08-21T07:27:04Z"
    message: kubelet is posting ready status
    reason: KubeletReady
    status: "True"
    type: Ready
  daemonEndpoints:
    kubeletEndpoint:
      Port: 10250
  images:
  - names:
    - registry.aliyuncs.com/google_containers/etcd@sha256:dd75ec974b0a2a6f6bb47001ba09207976e625db898d1b16735528c009cb171c
    - registry.aliyuncs.com/google_containers/etcd:3.5.6-0
    sizeBytes: 299475478
  - names:
    - registry.aliyuncs.com/google_containers/kube-apiserver@sha256:5f6f091e7b3886b9068eb354fe29b4fbd3384e821930e405e47166065e5e859b
    - registry.aliyuncs.com/google_containers/kube-apiserver:v1.23.17
    sizeBytes: 130012137
  - names:
    - registry.aliyuncs.com/google_containers/kube-controller-manager@sha256:5495a48e110d4800f2e0dbd27dc2acb7e77cabb1340733519c349e8e04e6566f
    - registry.aliyuncs.com/google_containers/kube-controller-manager:v1.23.17
    sizeBytes: 119948546
  - names:
    - registry.aliyuncs.com/google_containers/kube-proxy@sha256:4b3d1119f5fdd8230965c758a467bdca7fdef84c05b3f45605095f67bd64b5ff
    - registry.aliyuncs.com/google_containers/kube-proxy:v1.23.17
    sizeBytes: 110840983
  - names:
    - flannel/flannel@sha256:c7214e3ce66191e45b8c2808c703a2a5674751e90f0f65aef0b404db0a22400c
    - flannel/flannel:v0.22.2
    sizeBytes: 70179967
  - names:
    - registry.aliyuncs.com/google_containers/kube-scheduler@sha256:eb140d570d1f3d441f49c18141a8d81fee03525e7882441cfe8823d81b634659
    - registry.aliyuncs.com/google_containers/kube-scheduler:v1.23.17
    sizeBytes: 51856655
  - names:
    - registry.aliyuncs.com/google_containers/coredns@sha256:5b6ec0d6de9baaf3e92d0f66cd96a25b9edbce8716f5f15dcd1a616b3abd590e
    - registry.aliyuncs.com/google_containers/coredns:v1.8.6
    sizeBytes: 46829283
  - names:
    - flannel/flannel-cni-plugin@sha256:ca6779c6ad63b77af8a00151cefc08578241197b9a6fe144b0e55484bc52b852
    - flannel/flannel-cni-plugin:v1.2.0
    sizeBytes: 8038625
  - names:
    - registry.aliyuncs.com/google_containers/pause@sha256:3d380ca8864549e74af4b29c10f9cb0956236dfb01c40ca076fb6c37253234db
    - registry.aliyuncs.com/google_containers/pause:3.6
    sizeBytes: 682696
  nodeInfo:
    architecture: amd64
    bootID: 8e821c8e-7934-4402-ad15-461d87588c60
    containerRuntimeVersion: docker://20.10.23
    kernelVersion: 3.10.0-1160.el7.x86_64
    kubeProxyVersion: v1.23.17
    kubeletVersion: v1.23.17
    machineID: 25ffa45cb6834fb0a7f70f8567485af8
    operatingSystem: linux
    osImage: CentOS Linux 7 (Core)
    systemUUID: 564D0B5B-3F3A-3F0C-7016-A769F6FA5BE5

查看节点的 Label:

[root@k8sm1 ~]# kubectl get nodes --show-labels
NAME              STATUS   ROLES                  AGE   VERSION    LABELS
k8sm1.stonecoding.net   Ready    control-plane,master   24h   v1.23.17   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8sm1.stonecoding.net,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=,node.kubernetes.io/exclude-from-external-load-balancers=
k8sn1.stonecoding.net   Ready    <none>                 24h   v1.23.17   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8sn1.stonecoding.net,kubernetes.io/os=linux
k8sn2.stonecoding.net   Ready    <none>                 24h   v1.23.17   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8sn2.stonecoding.net,kubernetes.io/os=linux

维护

维护节点的步骤如下:

  1. 使用 kubectl cordon 命令将节点标记为不可调度
[root@k8sm1 ~]# kubectl cordon k8sn2.stonecoding.net
node/k8sn2.stonecoding.net cordoned
  1. 使用 kubectl drain 安全驱逐节点上面所有 Pod 到其他节点
[root@k8sm1 ~]# kubectl drain k8sn2.stonecoding.net  --force --ignore-daemonsets --delete-emptydir-data
node/k8sn2.stonecoding.net already cordoned
WARNING: ignoring DaemonSet-managed Pods: ingress-nginx/ingress-nginx-controller-m5qqj, kube-flannel/kube-flannel-ds-5tf8x, kube-system/kube-proxy-t62d9
evicting pod kubernetes-dashboard/kubernetes-dashboard-fb8648fd9-t4c7k
evicting pod app/nginx-deployment-v118-69584f9576-d4cw6
evicting pod app/nginx-deployment-9456bbbf9-5w9qt
evicting pod ingress-nginx/ingress-nginx-admission-patch-gh9p5
pod/ingress-nginx-admission-patch-gh9p5 evicted
pod/nginx-deployment-9456bbbf9-5w9qt evicted
pod/kubernetes-dashboard-fb8648fd9-t4c7k evicted
pod/nginx-deployment-v118-69584f9576-d4cw6 evicted
node/k8sn2.stonecoding.net drained
  1. 维护完成后,使用 kubectl uncordon 恢复节点可调度
[root@k8sm1 ~]# kubectl uncordon k8sn2.stonecoding.net
node/k8sn2.stonecoding.net uncordoned

排错

查看日志:

[root@k8sm1 ~]# kubectl logs -f nginx-deployment-9456bbbf9-5w9qt -n app

kubernetes-troubleshooting

上次编辑于:
贡献者: stonebox,stone