Kubernetes
# 安装
Centos7 2c4g,虚拟机,k8s v1.26 3 台机器,1 个 master,2 个 node master:192.168.83.133 node1:192.168.83.134 node2:192.168.83.135
# 安装 docker
3 台机器都装 docker https://docs.docker.com/engine/install/centos/ (opens new window)
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
sudo yum install -y yum-utils
sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl start docker
sudo systemctl enable docker
# 环境配置
https://kubernetes.io/zh-cn/docs/setup/production-environment/container-runtimes/ (opens new window) https://kubernetes.io/zh-cn/docs/setup/production-environment/tools/kubeadm/install-kubeadm/ (opens new window)
# 关闭防火墙
systemctl stop firewalld
systemctl disable firewalld
# 设置 hostname
hostnamectl set-hostname k8s-master
hostnamectl set-hostname k8s-node1
hostnamectl set-hostname k8s-node2
# 添加 host
cat << EOF >> /etc/hosts
192.168.83.133 k8s-master
192.168.83.134 k8s-node1
192.168.83.135 k8s-node2
EOF
# 关闭 swap:
swapoff -a
sed -ri 's/.*swap.*/#&/' /etc/fstab
# 将 SELinux 设置为 permissive 模式(相当于将其禁用)
sudo setenforce 0
sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config
# 允许 iptables 检查桥接流量
sudo modprobe br_netfilter
lsmod | grep br_netfilter
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
# 设置所需的 sysctl 参数,参数在重新启动后保持不变
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
# 应用 sysctl 参数而不重新启动
sudo sysctl --system
# 安装 k8s 核心
你需要在每台机器上安装以下的软件包:
- kubeadm:用来初始化集群的指令。
- kubelet:在集群中的每个节点上用来启动 Pod 和容器等。
- kubectl:用来与集群通信的命令行工具。
# 卸载旧版本
yum remove -y kubelet kubeadm kubectl
# 配置 K8S 的 yum 源
# https://developer.aliyun.com/mirror/kubernetes
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[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 remove -y kubelet kubeadm kubectl
# 查看可以安装的版本
yum list kubelet --showduplicates | sort -r
# 安装kubelet、kubeadm、kubectl 指定版本
yum install -y kubelet-1.26.0 kubeadm-1.26.0 kubectl-1.26.0
# 开机启动 kubelet
systemctl enable kubelet && systemctl start kubelet
# 初始化 master
https://kubernetes.io/zh-cn/docs/reference/setup-tools/kubeadm/kubeadm-init/#without-internet-connection (opens new window) 镜像地址在国外,下载不了,使用自定义镜像https://kubernetes.io/zh-cn/docs/reference/setup-tools/kubeadm/kubeadm-init/#custom-images (opens new window)
# 需要下载的镜像
[root@k8s-master ~]# kubeadm config images list
registry.k8s.io/kube-apiserver:v1.26.0
registry.k8s.io/kube-controller-manager:v1.26.0
registry.k8s.io/kube-scheduler:v1.26.0
registry.k8s.io/kube-proxy:v1.26.0
registry.k8s.io/pause:3.9
registry.k8s.io/etcd:3.5.6-0
registry.k8s.io/coredns/coredns:v1.9.3
编写 shell 脚本,拉取所需镜像,阿里云镜像不支持二级仓库,coredns 是二级仓库,需要额外处理
node 也需要下载镜像,但是只需要部分镜像,懒的话直接把整个 shell 脚本在 node 上执行
#!/bin/bash
images=(
kube-apiserver:v1.26.0
kube-controller-manager:v1.26.0
kube-scheduler:v1.26.0
kube-proxy:v1.26.0
pause:3.9
etcd:3.5.6-0
coredns:v1.9.3
)
pause="pause:3.9"
coredns="coredns:v1.9.3"
for imageName in ${images[@]} ; do
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/$imageName
if [[ $imageName == $pause ]]
then
docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/$pause registry.k8s.io/$pause
fi
if [[ $imageName == $coredns ]]
then
docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/$coredns registry.cn-hangzhou.aliyuncs.com/google_containers/coredns/$coredns
fi
done
chmod +x image.sh && ./image.sh
# kubernetes-version #制定 kubernetes 版本
# image-repository #选择用于拉取镜像的容器仓库
# apiserver-advertise-address #当前 Master 主机的ip地址
# service-cidr #为服务的虚拟 IP 地址段,默认值:"10.96.0.0/12"
# pod-network-cidr #指明 pod 网络可以使用的 IP 地址段。如果设置了这个参数,控制平面将会为每一个节点自动分配 CIDR
kubeadm init \
--kubernetes-version=1.26.0 \
--image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers \
--apiserver-advertise-address=192.168.83.133 \
--service-cidr=10.96.0.0/16 \
--pod-network-cidr=192.169.0.0/16
初始化失败container runtime is not running (opens new window)
rm -rf /etc/containerd/config.toml
systemctl restart containerd
kubeadm init
初始化失败The kubelet is not running;journalctl -xeu kubelet (opens new window)
k8s 弃用 dockershim 的问题,直接用 containerd,所以 docker pull 的镜像无效,需要在 container的 里面 pull 和 tag https://github.com/containerd/cri/blob/master/docs/registry.md (opens new window) 配置镜像加速
crictl pull registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.6
ctr -n k8s.io i tag registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.6 registry.k8s.io/pause:3.6
init 执行完输出
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:
# 应用网络插件
kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml
https://kubernetes.io/docs/concepts/cluster-administration/addons/
Then you can join any number of worker nodes by running the following on each as root:
# 其他 node 加入到 master
kubeadm join 192.168.83.133:6443 --token tu5nmc.364j7pr4ycu90ftb \
--discovery-token-ca-cert-hash sha256:a75892528f16685719627de7c72614d66f59cf67faf294c6cff437d066dd192e
# 查看所有应用
kubectl get pod -A
# 查看所有节点
kubectl get node -A
节点一直是 Pending 状态
# 查看详细信息,1 node(s) had untolerated taint {node.kubernetes.io/not-ready: }
kubectl describe pod coredns-567c556887-6d8p5 -n kube-system
# 清除污点,才能创建 pod
kubectl taint nodes $(hostname) node-role.kubernetes.io/control-plane:NoSchedule-
kubectl taint nodes $(hostname) node.kubernetes.io/not-ready:NoSchedule-
cni 插件未初始化,cni plugin not initialized https://www.bilibili.com/read/cv16293585 (opens new window)
# 重启 containerd
systemctl restart containerd
设置 master 为污点,让接下来的 pod 都在 node 创建
kubectl taint nodes $(hostname) check=master:NoSchedule
# 初始化 node
配置镜像加速
[root@k8s-node1 ~]# cat /etc/containerd/config.toml
version = 2
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint = ["https://v89g18tc.mirror.aliyuncs.com"]
kubeadm join 192.168.83.133:6443 --token tu5nmc.364j7pr4ycu90ftb \
--discovery-token-ca-cert-hash sha256:a75892528f16685719627de7c72614d66f59cf67faf294c6cff437d066dd192e
# 设置 IPVS
IPVS 代理模式基于 netfilter 回调函数,类似于 iptables 模式, 但它使用哈希表作为底层数据结构,在内核空间中生效。 这意味着 IPVS 模式下的 kube-proxy 比 iptables 模式下的 kube-proxy 重定向流量的延迟更低,同步代理规则时性能也更好。 与其他代理模式相比,IPVS 模式还支持更高的网络流量吞吐量。
#1、查看默认kube-proxy 使用的模式
kubectl logs -n kube-system kube-proxy-28xv4
#2、需要修改 kube-proxy 的配置文件,修改mode 为ipvs。默认iptables,但是集群大了以后就很慢
kubectl edit cm kube-proxy -n kube-system
# 将 mode 设置为 ipvs
mode: "ipvs"
###修改了kube-proxy的配置,为了让重新生效,需要杀掉以前的Kube-proxy
kubectl get pod -A|grep kube-proxy
kubectl delete pod kube-proxy-pqgnt -n kube-system
### 修改完成后可以重启kube-proxy以生效
kubectl logs -n kube-system kube-proxy-28xv4
设置 node 标签
[root@k8s-master net.d]# kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-master Ready control-plane 5h36m v1.26.0
k8s-node1 Ready <none> 85m v1.26.0
k8s-node2 Ready <none> 85m v1.26.0
[root@k8s-master net.d]# kubectl label node k8s-node1 node-role.kubernetes.io/worker=
node/k8s-node1 labeled
[root@k8s-master net.d]# kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-master Ready control-plane 5h37m v1.26.0
k8s-node1 Ready worker 85m v1.26.0
k8s-node2 Ready <none> 85m v1.26.0
[root@k8s-master net.d]# kubectl label node k8s-node2 node-role.kubernetes.io/worker=
node/k8s-node2 labeled
[root@k8s-master net.d]# kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-master Ready control-plane 5h37m v1.26.0
k8s-node1 Ready worker 85m v1.26.0
k8s-node2 Ready worker 86m v1.26.0
[root@k8s-master net.d]#
# 入门
# 了解应用
# kubectl create 帮我们创建k8s集群中的一些对象
kubectl create --help
#Create a deployment named my-nginx that runs the nginx image
kubectl create deployment my-nginx --image=nginx
# Create a deployment with command
kubectl create deployment my-nginx --image=nginx --date
# Create a deployment named my-nginx that runs the nginx image with 3 replicas.
kubectl create deployment my-nginx --image=nginx --replicas=3
# Create a deployment named my-nginx that runs the nginx image and expose container port 80.
kubectl create deployment my-nginx --image=nginx --port=80
kubectl get
- 列出资源kubectl describe
- 显示有关资源的详细信息kubectl logs
- 打印 pod 和其中容器的日志kubectl exec
- 在 pod 中的容器上执行命令
kubectl get - 显示资源列表
# kubectl get 资源类型
#获取类型为Deployment的资源列表
kubectl get deployments
#获取类型为Pod的资源列表
kubectl get pods
#获取类型为Node的资源列表
kubectl get nodes
# 查看所有名称空间的 Deployment
kubectl get deployments -A
kubectl get deployments --all-namespaces
# 查看 kube-system 名称空间的 Deployment
kubectl get deployments -n kube-system
#####并不是所有的对象都在名称空间中
# 在名称空间里
kubectl api-resources --namespaced=true
# 不在名称空间里
kubectl api-resources --namespaced=false
kubectl describe - 显示有关资源的详细信息
# kubectl describe 资源类型 资源名称
#查看名称为nginx-XXXXXX的Pod的信息
kubectl describe pod nginx-XXXXXX
#查看名称为nginx的Deployment的信息
kubectl describe deployment my-nginx
kubectl logs - 查看pod中的容器的打印日志(和命令docker logs 类似)
# kubectl logs Pod名称
#查看名称为nginx-pod-XXXXXXX的Pod内的容器打印的日志
#本案例中的 nginx-pod 没有输出日志,所以您看到的结果是空的
kubectl logs -f nginx-pod-XXXXXXX
kubectl exec - 在pod中的容器环境内执行命令(和命令docker exec 类似)
# kubectl exec Pod名称 操作命令
# 在名称为nginx-pod-xxxxxx的Pod中运行bash
kubectl exec -it nginx-pod-xxxxxx -- /bin/bash
kubectl run也可以独立跑一个Pod
## kubectl run --help
kubectl run nginx --image=nginx
# 暴露应用
尽管每个 Pod 都有一个唯一的 IP 地址,但是如果没有 Service ,这些 IP 不会暴露在集群外部。Service 允许你的应用程序接收流量。Service 也可以用在 ServiceSpec 标记type的方式暴露
- ClusterIP (默认) - 在集群的内部 IP 上公开 Service 。这种类型使得 Service 只能从集群内访问。
- NodePort - 使用 NAT 在集群中每个选定 Node 的相同端口上公开 Service 。使用<NodeIP>:<NodePort> 从集群外部访问 Service。是 ClusterIP 的超集。
- LoadBalancer - 在当前云中创建一个外部负载均衡器(如果支持的话),并为 Service 分配一个固定的外部IP。是 NodePort 的超集。
- ExternalName - 通过返回带有该名称的 CNAME 记录,使用任意名称(由 spec 中的externalName指定)公开 Service。不使用代理。这种类型需要kube-dns的v1.7或更高版本。
Service 匹配一组 Pod 是使用 标签(Label)和选择器(Selector) (opens new window), 它们是允许对 Kubernetes 中的对象进行逻辑操作的一种分组原语。标签(Label)是附加在对象上的键/值对,可以以多种方式使用:
- 指定用于开发,测试和生产的对象
- 嵌入版本标签
- 使用 Label 将对象进行分类
kubectl expose deploy my-nginx-rep3 --port=8000 --target-port=80 --type=NodePort
## --port:集群内访问service的端口 8000
## --target-port: pod容器的端口 80
## --nodePort: 每个node机器开放的端口 32409
kubectl get services
# 修改 pod 上的页面
kubectl exec -it pod/my-nginx-rep3-55d7ff6bc7-f48xx -- bin/bash
echo 111 > /usr/share/nginx/html/index.html
##### 测试负载均衡
[root@k8s-master net.d]# curl 10.96.197.158:8000
222
[root@k8s-master net.d]# curl 10.96.197.158:8000
111
[root@k8s-master net.d]# curl 10.96.197.158:8000
333
[root@k8s-master net.d]# curl k8s-node1:32409
222
[root@k8s-master net.d]# curl k8s-node1:32409
111
[root@k8s-master net.d]# curl k8s-node1:32409
333
# 打标签 key=value
kubectl label service/my-ngin aaa=bb
# 去标签 key-
kubectl label service/my-ngin aaa-
# 通过标签选择
kubectl delete svc -l aaa
# 扩缩容应用
在之前的模块中,我们创建了一个 Deployment (opens new window),然后通过 Service (opens new window)让其可以开放访问。Deployment 仅为跑这个应用程序创建了一个 Pod。 当流量增加时,我们需要扩容应用程序满足用户需求。 扩缩 是通过改变 Deployment 中的副本数量来实现的。
kubectl scale --replicas=4 deployment.apps/my-nginx-rep3
# 更新应用
用户希望应用程序始终可用,而开发人员则需要每天多次部署它们的新版本。在 Kubernetes 中,这些是通过滚动更新(Rolling Updates)完成的。 滚动更新 允许通过使用新的实例逐步更新 Pod 实例,零停机进行 Deployment 更新。新的 Pod 将在具有可用资源的节点上进行调度。 在前面的模块中,我们将应用程序扩展为运行多个实例。这是在不影响应用程序可用性的情况下执行更新的要求。默认情况下,更新期间不可用的 pod 的最大值和可以创建的新 pod 数都是 1。这两个选项都可以配置为(pod)数字或百分比。 在 Kubernetes 中,更新是经过版本控制的,任何 Deployment 更新都可以恢复到以前的(稳定)版本。 滚动更新允许以下操作:
- 将应用程序从一个环境提升到另一个环境(通过容器镜像更新)
- 回滚到以前的版本
- 持续集成和持续交付应用程序,无需停机
# 更新镜像 nginx(容器名)=nginx:1.23(镜像)
# --record=true 记录命令
kubectl set image deployment.apps/my-nginx-rep3 nginx=nginx:1.23 --record=true
# 查看指定版本的变更详情
kubectl rollout history deployment.apps/my-nginx-rep3 --revision=2
# 查看滚动升级状态
kubectl rollout status deployment.apps/my-nginx-rep3
# 回归到上一个版本
kubectl rollout undo deployment.apps/my-nginx-rep3
# 回滚到指定版本
kubectl rollout undo deployment.apps/my-nginx-rep3 --to-revision=1
# 配置文件
# 与k8s集群版本有关,使用 kubectl api-resources 即可查看当前集群支持的版本
apiVersion: apps/v1
# 配置类型 Deployment Service ...
kind: Deployment
# Deployment的元数据
metadata:
# 标签
labels:
app: nginx
# 该名称将成为后续创建 ReplicaSet 和 Pod 的命名基础
name: nginx-deployment
# 命名空间
namespace: default
# Deployment 的描述,在 k8s 上如何运行
spec:
# 3个副本
replicas: 3
# 标签选择器,ReplicaSet 如何查找要管理的 Pod
selector:
matchLabels:
app: nginx
# pod 模板
template:
# pod 元数据
metadata:
labels:
app: nginx
# pod 要实现的功能
spec:
# 使用的镜像
containers:
# 镜像
- image: nginx:latest
# 容器名
name: nginx
apiVersion: v1
kind: Service
metadata:
# service 名称
name: nginx-service
# service 自己的标签
labels:
app: nginx
spec:
# 标签选择器
selector:
# 选择包含 app=nginx 的 pod
app: nginx
ports:
# 端口名
- name: nginx-port
# 协议类型
protocol: TCP
# 集群端口
port: 80
# 机器端口
nodePort: 32600
# pod 端口
targetPort: 80
# service 类型
type: NodePort
- 扩缩容
修改deployment.yaml
中的 replicas
属性即可
完成后运行 kubectl apply -f xxx.yaml
- 滚动升级
修改 deployment.yaml
中的 imageName
属性等
完成后运行 kubectl apply -f xxx.yaml
以上都可以直接
kubectl edit deploy/service
等,修改完成后自动生效
# 命令自动补全
https://kubernetes.io/zh-cn/docs/tasks/tools/included/optional-kubectl-configs-bash-linux/ (opens new window) https://kubernetes.io/zh-cn/docs/reference/kubectl/cheatsheet/ (opens new window)
yum install bash-completion
kubectl completion bash | sudo tee /etc/bash_completion.d/kubectl > /dev/null
echo 'alias k=kubectl' >>~/.bashrc
echo 'complete -o default -F __start_kubectl k' >>~/.bashrc
exec bash
# YAML 插件
- VSCode 装 Kubernetes Templates https://marketplace.visualstudio.com/items?itemName=lunuan.kubernetes-templates (opens new window)
- IDEA 装 kubernetes https://plugins.jetbrains.com/plugin/10485-kubernetes (opens new window)
# Dashboard
https://github.com/kubernetes/dashboard (opens new window)
- 默认 svc 的 type 是 ClusterIP 只能内网访问,改为 NodePort 暴露端口
- 访问地址,机器 ip + nodePort
- 创建用户 https://github.com/kubernetes/dashboard/blob/master/docs/user/access-control/creating-sample-user.md (opens new window)
- 创建服务帐户
apiVersion: v1
kind: ServiceAccount
metadata:
name: admin-user
namespace: kubernetes-dashboard
- 创建 ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: admin-user
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: admin-user
namespace: kubernetes-dashboard
- 获取 token
kubectl -n kubernetes-dashboard create token admin-user
# 概念
# 更新镜像
https://kubernetes.io/zh-cn/docs/concepts/containers/images/#image-names (opens new window)
# 镜像拉取策略
容器的 imagePullPolicy 和镜像的标签会影响 kubelet (opens new window) 尝试拉取(下载)指定的镜像。
- **IfNotPresent **只有当镜像在本地不存在时才会拉取。
- **Always **每当 kubelet 启动一个容器时,kubelet 会查询容器的镜像仓库, 将名称解析为一个镜像摘要 (opens new window)。 如果 kubelet 有一个容器镜像,并且对应的摘要已在本地缓存,kubelet 就会使用其缓存的镜像; 否则,kubelet 就会使用解析后的摘要拉取镜像,并使用该镜像来启动容器。
- **Never **Kubelet 不会尝试获取镜像。如果镜像已经以某种方式存在本地, kubelet 会尝试启动容器;否则,会启动失败。 更多细节见提前拉取镜像 (opens new window)。
只要能够可靠地访问镜像仓库,底层镜像提供者的缓存语义甚至可以使 imagePullPolicy: Always 高效。 你的容器运行时可以注意到节点上已经存在的镜像层,这样就不需要再次下载。
# 默认镜像拉取策略
当你(或控制器)向 API 服务器提交一个新的 Pod 时,你的集群会在满足特定条件时设置 imagePullPolicy 字段:
- 如果你省略了 imagePullPolicy 字段,并且容器镜像的标签是 :latest, imagePullPolicy 会自动设置为 Always。
- 如果你省略了 imagePullPolicy 字段,并且没有指定容器镜像的标签, imagePullPolicy 会自动设置为 Always。
- 如果你省略了 imagePullPolicy 字段,并且为容器镜像指定了非 :latest 的标签, imagePullPolicy 就会自动设置为 IfNotPresent。
# 必要的镜像拉取
如果你想总是强制执行拉取,你可以使用下述的一中方式:
- 设置容器的 imagePullPolicy 为 Always。
- 省略 imagePullPolicy,并使用 :latest 作为镜像标签; 当你提交 Pod 时,Kubernetes 会将策略设置为 Always。
- 省略 imagePullPolicy 和镜像的标签; 当你提交 Pod 时,Kubernetes 会将策略设置为 Always。
- 启用准入控制器 AlwaysPullImages (opens new window)。
# 使用私有仓库
从私有仓库读取镜像时可能需要密钥。 凭据可以用以下方式提供:
- 配置节点向私有仓库进行身份验证
- 所有 Pod 均可读取任何已配置的私有仓库
- 需要集群管理员配置节点
- kubelet 凭据提供程序,动态获取私有仓库的凭据
- kubelet 可以被配置为使用凭据提供程序 exec 插件来访问对应的私有镜像库
- 预拉镜像
- 所有 Pod 都可以使用节点上缓存的所有镜像
- 需要所有节点的 root 访问权限才能进行设置
- 在 Pod 中设置 ImagePullSecrets
- 只有提供自己密钥的 Pod 才能访问私有仓库
- 特定于厂商的扩展或者本地扩展
- 如果你在使用定制的节点配置,你(或者云平台提供商)可以实现让节点向容器仓库认证的机制
# 在 Pod 中设置 ImagePullSecrets
# kubectl create secret docker-registry <name> --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL
k create ns my-ns
kubectl create secret docker-registry aliyun-docker \
--docker-server=registry.cn-hongkong.aliyuncs.com \
--docker-username=starries \
--docker-password=lol101.com \
--docker-email=DOCKER_EMAIL \
-n my-ns
Pod 只能引用位于自身所在名字空间中的 Secret,因此需要针对每个名字空间重复执行上述过程。
apiVersion: v1
kind: Pod
metadata:
name: "private-app"
namespace: my-ns
labels:
app: "private-app"
spec:
imagePullSecrets:
- name: aliyun-docker
containers:
- name: private-app
image: "registry.cn-hongkong.aliyuncs.com/space_starry/java-devops-demo:1.0"
restartPolicy: Always
# 容器的命令和参数
https://kubernetes.io/zh-cn/docs/tasks/inject-data-application/define-command-argument-container/ (opens new window) 创建 Pod 时,可以为其下的容器设置启动时要执行的命令及其参数。 如果要设置命令,就填写在配置文件的 command 字段下, 如果要设置命令的参数,就填写在配置文件的 args 字段下。 一旦 Pod 创建完成,该命令及其参数就无法再进行更改了。 如果在配置文件中设置了容器启动时要执行的命令及其参数,那么容器镜像中自带的命令与参数将会被覆盖而不再执行。 如果配置文件中只是设置了参数,却没有设置其对应的命令,那么容器镜像中自带的命令会使用该新参数作为其执行时的参数。
# 容器生命周期回调
有两个回调暴露给容器:
- PostStart 这个回调在容器被创建之后立即被执行。 但是,不能保证回调会在容器入口点(ENTRYPOINT)之前执行。 没有参数传递给处理程序。
- PreStop 在容器因 API 请求或者管理事件(诸如存活态探针、启动探针失败、资源抢占、资源竞争等) 而被终止之前,此回调会被调用。 如果容器已经处于已终止或者已完成状态,则对 preStop 回调的调用将失败。 在用来停止容器的 TERM 信号被发出之前,回调必须执行结束。 Pod 的终止宽限周期在 PreStop 回调被执行之前即开始计数, 所以无论回调函数的执行结果如何,容器最终都会在 Pod 的终止宽限期内被终止。 没有参数会被传递给处理程序。
apiVersion: v1
kind: Pod
metadata:
name: "lifecycle-app"
namespace: my-ns
labels:
app: "lifecycle-app"
spec:
imagePullSecrets:
- name: aliyun-docker
containers:
- name: lifecycle-app
image: "registry.cn-hongkong.aliyuncs.com/space_starry/java-devops-demo:1.0"
lifecycle:
postStart:
exec:
command: ["/bin/bash", "-c", "echo hello world...;"]
preStop:
exec:
command: ["/bin/bash", "-c", "echo dead world...;"]
restartPolicy: Always
# 创建静态 Pod
静态 Pod 在指定的节点上由 kubelet 守护进程直接管理,不需要 API 服务器 (opens new window)监管。 与由控制面管理的 Pod(例如,Deployment (opens new window)) 不同;kubelet 监视每个静态 Pod(在它失败之后重新启动)。 静态 Pod 始终都会绑定到特定节点的 Kubelet (opens new window) 上。 kubelet 会尝试通过 Kubernetes API 服务器为每个静态 Pod 自动创建一个镜像 Pod (opens new window)。 这意味着节点上运行的静态 Pod 对 API 服务来说是可见的,但是不能通过 API 服务器来控制。 Pod 名称将把以连字符开头的节点主机名作为后缀。
如果你在运行一个 Kubernetes 集群,并且在每个节点上都运行一个静态 Pod, 就可能需要考虑使用 DaemonSet (opens new window) 替代这种方式。 静态 Pod 的 spec 不能引用其他 API 对象 (如:ServiceAccount (opens new window)、 ConfigMap (opens new window)、 Secret (opens new window) 等)。 静态 Pod 不支持临时容器 (opens new window)。
# 工作负载
Kubernetes 提供若干种内置的工作负载资源:
- Deployment (opens new window) 和 ReplicaSet (opens new window) (替换原来的资源 ReplicationController (opens new window))。 Deployment 很适合用来管理你的集群上的无状态应用,Deployment 中的所有 Pod 都是相互等价的,并且在需要的时候被替换。
- StatefulSet (opens new window) 让你能够运行一个或者多个以某种方式跟踪应用状态的 Pod。 例如,如果你的负载会将数据作持久存储,你可以运行一个 StatefulSet,将每个 Pod 与某个 PersistentVolume (opens new window) 对应起来。你在 StatefulSet 中各个 Pod 内运行的代码可以将数据复制到同一 StatefulSet 中的其它 Pod 中以提高整体的服务可靠性。
- DaemonSet (opens new window) 定义提供节点本地支撑设施的 Pod。这些 Pod 可能对于你的集群的运维是 非常重要的,例如作为网络链接的辅助工具或者作为网络 插件 (opens new window) 的一部分等等。每次你向集群中添加一个新节点时,如果该节点与某 DaemonSet 的规约匹配,则控制平面会为该 DaemonSet 调度一个 Pod 到该新节点上运行。
- Job (opens new window) 和 CronJob (opens new window)。 定义一些一直运行到结束并停止的任务。Job 用来执行一次性任务,而 CronJob 用来执行的根据时间规划反复运行的任务。
在庞大的 Kubernetes 生态系统中,你还可以找到一些提供额外操作的第三方工作负载相关的资源。 通过使用定制资源定义(CRD) (opens new window), 你可以添加第三方工作负载资源,以完成原本不是 Kubernetes 核心功能的工作。 例如,如果你希望运行一组 Pod,但要求所有 Pod 都可用时才执行操作 (比如针对某种高吞吐量的分布式任务),你可以基于定制资源实现一个能够满足这一需求的扩展, 并将其安装到集群中运行。
# 容器探针
probe 是由 kubelet (opens new window) 对容器执行的定期诊断。 要执行诊断,kubelet 既可以在容器内执行代码,也可以发出一个网络请求。
# 检查机制
使用探针来检查容器有四种不同的方法。 每个探针都必须准确定义为这四种机制中的一种: exec 在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。 grpc 使用 gRPC (opens new window) 执行一个远程过程调用。 目标应该实现 gRPC健康检查 (opens new window)。 如果响应的状态是 "SERVING",则认为诊断成功。 gRPC 探针是一个 Alpha 特性,只有在你启用了 "GRPCContainerProbe" 特性门控 (opens new window)时才能使用。 httpGet 对容器的 IP 地址上指定端口和路径执行 HTTP GET 请求。如果响应的状态码大于等于 200 且小于 400,则诊断被认为是成功的。 tcpSocket 对容器的 IP 地址上的指定端口执行 TCP 检查。如果端口打开,则诊断被认为是成功的。 如果远程系统(容器)在打开连接后立即将其关闭,这算作是健康的。
# 探测结果
每次探测都将获得以下三种结果之一: Success(成功) 容器通过了诊断。 Failure(失败) 容器未通过诊断。 Unknown(未知) 诊断失败,因此不会采取任何行动。
# 探测类型
针对运行中的容器,kubelet 可以选择是否执行以下三种探针,以及如何针对探测结果作出反应: livenessProbe 指示容器是否正在运行。如果存活态探测失败,则 kubelet 会杀死容器, 并且容器将根据其重启策略 (opens new window)决定未来。如果容器不提供存活探针, 则默认状态为 Success。 readinessProbe 指示容器是否准备好为请求提供服务。如果就绪态探测失败, 端点控制器将从与 Pod 匹配的所有服务的端点列表中删除该 Pod 的 IP 地址。 初始延迟之前的就绪态的状态值默认为 Failure。 如果容器不提供就绪态探针,则默认状态为 Success。 startupProbe 指示容器中的应用是否已经启动。如果提供了启动探针,则所有其他探针都会被 禁用,直到此探针成功为止。如果启动探测失败,kubelet 将杀死容器, 而容器依其重启策略 (opens new window)进行重启。 如果容器没有提供启动探测,则默认状态为 Success。
# Deployments
一个 Deployment 为 Pod (opens new window) 和 ReplicaSet (opens new window) 提供声明式的更新能力。 你负责描述 Deployment 中的 目标状态,而 Deployment 控制器(Controller) (opens new window) 以受控速率更改实际状态, 使其变为期望状态。你可以定义 Deployment 以创建新的 ReplicaSet,或删除现有 Deployment, 并通过新的 Deployment 收养其资源。 以下是 Deployments 的典型用例
- 创建 Deployment 以将 ReplicaSet 上线 (opens new window)。ReplicaSet 在后台创建 Pod。 检查 ReplicaSet 的上线状态,查看其是否成功。
- 通过更新 Deployment 的 PodTemplateSpec,声明 Pod 的新状态 (opens new window) 。 新的 ReplicaSet 会被创建,Deployment 以受控速率将 Pod 从旧 ReplicaSet 迁移到新 ReplicaSet。 每个新的 ReplicaSet 都会更新 Deployment 的修订版本。
- 如果 Deployment 的当前状态不稳定,回滚到较早的 Deployment 版本 (opens new window)。 每次回滚都会更新 Deployment 的修订版本。
- 扩大 Deployment 规模以承担更多负载 (opens new window)。
- 暂停 Deployment 的上线 (opens new window) 以应用对 PodTemplateSpec 所作的多项修改, 然后恢复其执行以启动新的上线版本。
- 使用 Deployment 状态 (opens new window)来判定上线过程是否出现停滞。
- 清理较旧的不再需要的 ReplicaSet (opens new window) 。
# HPA
HorizontalPodAutoscaler (opens new window)(简称 HPA ) 自动更新工作负载资源(例如 Deployment (opens new window) 或者 StatefulSet (opens new window)), 目的是自动扩缩工作负载以满足需求。 水平扩缩意味着对增加的负载的响应是部署更多的 Pod (opens new window)。 这与 “垂直(Vertical)” 扩缩不同,对于 Kubernetes, 垂直扩缩意味着将更多资源(例如:内存或 CPU)分配给已经为工作负载运行的 Pod。 如果负载减少,并且 Pod 的数量高于配置的最小值, HorizontalPodAutoscaler 会指示工作负载资源(Deployment、StatefulSet 或其他类似资源)缩减。
- 安装 metrics-server https://github.com/kubernetes-sigs/metrics-server#readme (opens new window)
- https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml (opens new window)
Kubelet 证书需要由集群证书颁发机构签名(或通过传递--kubelet-insecure-tls给 Metrics Server 来禁用证书验证) 在 containers 的 args 中 添加
--kubelet-insecure-tls
- 验证
k get nodes
k get pods
# 从 dashboard 查看多了内存和 CPU 占用
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-apache
spec:
selector:
matchLabels:
run: php-apache
replicas: 1
template:
metadata:
labels:
run: php-apache
spec:
containers:
- name: php-apache
image: registry.cn-hangzhou.aliyuncs.com/lfy_k8s_images/php-hpa:latest
ports:
- containerPort: 80
resources:
limits:
cpu: 500m
requests:
cpu: 200m
---
apiVersion: v1
kind: Service
metadata:
name: php-apache
labels:
run: php-apache
spec:
ports:
- port: 80
selector:
run: php-apache
k apply -f php-apache.yaml
# 以保持所有 Pod 的平均 CPU 利用率为 50%
kubectl autoscale deployment php-apache --cpu-percent=50 --min=1 --max=10
# 查看 hpa
kubectl get hpa
# 压测
while sleep 0.01; do curl http://10.96.187.173; done
# 新开窗口监控 hpa 资源
k get hpa -w
# 新开窗口监控 pod 资源
k get pod -w
[root@k8s-master k8s-yaml]# k get hpa php-apache -o yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
creationTimestamp: "2023-01-08T10:00:04Z"
name: php-apache
namespace: default
resourceVersion: "1432675"
uid: 07cccb23-bc59-4e57-9c7f-dcfe5647e9b9
spec:
maxReplicas: 10
metrics:
- resource:
name: cpu
target:
averageUtilization: 50
type: Utilization
type: Resource
minReplicas: 1
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
# 金丝雀部署
一个 service,管理 2 个不同版本的 pod,pod 带有相同的标签和不同的标签,根据 pod 数量不同,请求比例不同。请求稳定后,删除旧版本 pod,所有流量切换到新 pod
# 蓝绿部署
部署 2 份(旧 pod 和新 pod)相同数量的 pod,直接修改 Service 的标签,流量直接切换到新 pod。
selector:
app: nginx
version: v1
# ===>
selector:
app: nginx
version: v2
# StatefulSet
StatefulSet 是用来管理有状态应用的工作负载 API 对象。 StatefulSet 用来管理某 Pod (opens new window) 集合的部署和扩缩, 并为这些 Pod 提供持久存储和持久标识符。 和 Deployment (opens new window) 类似, StatefulSet 管理基于相同容器规约的一组 Pod。但和 Deployment 不同的是, StatefulSet 为它们的每个 Pod 维护了一个有粘性的 ID。这些 Pod 是基于相同的规约来创建的, 但是不能相互替换:无论怎么调度,每个 Pod 都有一个永久不变的 ID。
# 使用场景
StatefulSet 对于需要满足以下一个或多个需求的应用程序很有价值:
- 稳定的、唯一的网络标识符。
- 稳定的、持久的存储。
- 有序的、优雅的部署和扩缩。
- 有序的、自动的滚动更新。
在上面描述中,“稳定的”意味着 Pod 调度或重调度的整个过程是有持久性的。 如果应用程序不需要任何稳定的标识符或有序的部署、删除或扩缩, 则应该使用由一组无状态的副本控制器提供的工作负载来部署应用程序,比如 Deployment (opens new window) 或者 ReplicaSet (opens new window) 可能更适用于你的无状态应用部署需要。
# 限制
- 给定 Pod 的存储必须由 PersistentVolume Provisioner (opens new window) 基于所请求的 storage class 来制备,或者由管理员预先制备。
- 删除或者扩缩 StatefulSet 并不会删除它关联的存储卷。 这样做是为了保证数据安全,它通常比自动清除 StatefulSet 所有相关的资源更有价值。
- StatefulSet 当前需要无头服务 (opens new window)来负责 Pod 的网络标识。你需要负责创建此服务。
- 当删除一个 StatefulSet 时,该 StatefulSet 不提供任何终止 Pod 的保证。 为了实现 StatefulSet 中的 Pod 可以有序且体面地终止,可以在删除之前将 StatefulSet 缩容到 0。
- 在默认 Pod 管理策略 (opens new window)(OrderedReady) 时使用滚动更新 (opens new window), 可能进入需要人工干预 (opens new window)才能修复的损坏状态。
apiVersion: v1
kind: Service
metadata:
name: statefulset-nginx
namespace: default
spec:
selector:
app: statefulset-nginx
type: ClusterIP
# 指定为 None,表示 head less,无头服务,使用 dns 访问
clusterIP: None
ports:
- name: web
protocol: TCP
port: 80
targetPort: 80
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: statefulset-nginx
namespace: default
spec:
selector:
matchLabels:
app: statefulset-nginx
serviceName: "statefulset-nginx"
replicas: 3
template:
metadata:
labels:
app: statefulset-nginx
spec:
terminationGracePeriodSeconds: 10
containers:
- name: statefulset-nginx
image: nginx:latest
imagePullPolicy: Never
ports:
- containerPort: 80
name: nginx
# 稳定的网络 ID
集群域名 | 服务(名字空间/名字) | StatefulSet(名字空间/名字) | StatefulSet 域名 | Pod DNS | Pod 主机名 |
---|---|---|---|---|---|
cluster.local | default/nginx | default/web | nginx.default.svc.cluster.local | web-{0..N-1}.nginx.default.svc.cluster.local | web-{0..N-1} |
cluster.local | foo/nginx | foo/web | nginx.foo.svc.cluster.local | web-{0..N-1}.nginx.foo.svc.cluster.local | web-{0..N-1} |
kube.local | foo/nginx | foo/web | nginx.foo.svc.kube.local | web-{0..N-1}.nginx.foo.svc.kube.local | web-{0..N-1} |
集群域会被设置为 cluster.local,除非有其他配置 (opens new window)。
# DaemonSet
DaemonSet 确保全部(或者某些)节点上运行一个 Pod 的副本。 当有节点加入集群时, 也会为他们新增一个 Pod 。 当有节点从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod。 DaemonSet 的一些典型用法:
- 在每个节点上运行集群守护进程
- 在每个节点上运行日志收集守护进程
- 在每个节点上运行监控守护进程
一种简单的用法是为每种类型的守护进程在所有的节点上都启动一个 DaemonSet。 一个稍微复杂的用法是为同一种守护进程部署多个 DaemonSet;每个具有不同的标志, 并且对不同硬件类型具有不同的内存、CPU 要求。
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: logging
namespace: default
labels:
app: logging
spec:
selector:
matchLabels:
app: logging
template:
metadata:
labels:
app: logging
spec:
containers:
- name: logging
image: nginx:latest
imagePullPolicy: Never
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
terminationGracePeriodSeconds: 3
# 每个 node 上都部署
kubectl get pod -l app=logging -o wide
# Job
Job 会创建一个或者多个 Pod,并将继续重试 Pod 的执行,直到指定数量的 Pod 成功终止。
- 随着 Pod 成功结束,Job 跟踪记录成功完成的 Pod 个数。
- 当数量达到指定的成功个数阈值时,任务(即 Job)结束。
- 删除 Job 的操作会清除所创建的全部 Pod。
- 挂起 Job 的操作会删除 Job 的所有活跃 Pod,直到 Job 被再次恢复执行。
一种简单的使用场景下,你会创建一个 Job 对象以便以一种可靠的方式运行某 Pod 直到完成。 当第一个 Pod 失败或者被删除(比如因为节点硬件失效或者重启)时,Job 对象会启动一个新的 Pod。 你也可以使用 Job 以并行的方式运行多个 Pod。 如果你想按某种排期表(Schedule)运行 Job(单个任务或多个并行任务),请参阅 CronJob (opens new window)。
apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
template:
spec:
containers:
- name: pi
image: perl:5.34.0
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
restartPolicy: Never
backoffLimit: 4
Job 终止与清理
- Job 完成时不会再创建新的 Pod,不过已有的 Pod 通常 (opens new window)也不会被删除。 保留这些 Pod 使得你可以查看已完成的 Pod 的日志输出,以便检查错误、警告或者其它诊断性输出。
- 默认情况下,Job 会持续运行,除非某个 Pod 失败(restartPolicy=Never) 或者某个容器出错退出(restartPolicy=OnFailure)。 这时,Job 基于前述的 spec.backoffLimit 来决定是否以及如何重试。 一旦重试次数到达 .spec.backoffLimit 所设的上限,Job 会被标记为失败, 其中运行的 Pod 都会被终止。
- 终止 Job 的另一种方式是设置一个活跃期限。 你可以为 Job 的 .spec.activeDeadlineSeconds 设置一个秒数值。 该值适用于 Job 的整个生命期,无论 Job 创建了多少个 Pod。 一旦 Job 运行时间达到 activeDeadlineSeconds 秒,其所有运行中的 Pod 都会被终止, 并且 Job 的状态更新为 type: Failed 及 reason: DeadlineExceeded。
注意 Job 的 .spec.activeDeadlineSeconds 优先级高于其 .spec.backoffLimit 设置。 因此,如果一个 Job 正在重试一个或多个失效的 Pod,该 Job 一旦到达 activeDeadlineSeconds 所设的时限即不再部署额外的 Pod, 即使其重试次数还未达到 backoffLimit 所设的限制。 TTL
- 自动清理已完成 Job (状态为 Complete 或 Failed)的另一种方式是使用由 TTL 控制器 (opens new window)所提供的 TTL 机制。 通过设置 Job 的 .spec.ttlSecondsAfterFinished 字段,可以让该控制器清理掉已结束的资源。
- TTL 控制器清理 Job 时,会级联式地删除 Job 对象。 换言之,它会删除所有依赖的对象,包括 Pod 及 Job 本身。
- 如果该字段设置为 0,Job 在结束之后立即成为可被自动删除的对象。 如果该字段没有设置,Job 不会在结束之后被 TTL 控制器自动清除。
- 建议设置 ttlSecondsAfterFinished 字段,执行垃圾回收,清理资源
# CronJob
CronJob 创建基于时隔重复调度的 Job (opens new window)。 一个 CronJob 对象就像 crontab (cron table) 文件中的一行。 它用 Cron (opens new window) 格式进行编写, 并周期性地在给定的调度时间执行 Job。
所有 CronJob 的 schedule: 时间都是基于 kube-controller-manager (opens new window) 的时区。 如果你的控制平面在 Pod 或是裸容器中运行了 kube-controller-manager, 那么为该容器所设置的时区将会决定 Cron Job 的控制器所使用的时区。
apiVersion: batch/v1
kind: CronJob
metadata:
name: hello
spec:
schedule: "* * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox:1.28
imagePullPolicy: IfNotPresent
command:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailure
Cron 时间表语法
# ┌───────────── 分钟 (0 - 59)
# │ ┌───────────── 小时 (0 - 23)
# │ │ ┌───────────── 月的某天 (1 - 31)
# │ │ │ ┌───────────── 月份 (1 - 12)
# │ │ │ │ ┌───────────── 周的某天 (0 - 6)(周日到周一;在某些系统上,7 也是星期日)
# │ │ │ │ │ 或者是 sun,mon,tue,web,thu,fri,sat
# │ │ │ │ │
# │ │ │ │ │
# * * * * *
CronJob 根据其计划编排,在每次该执行任务的时候大约会创建一个 Job。 我们之所以说 "大约",是因为在某些情况下,可能会创建两个 Job,或者不会创建任何 Job。 我们试图使这些情况尽量少发生,但不能完全杜绝。因此,Job 应该是 幂等的。
如果 startingDeadlineSeconds
设置为很大的数值或未设置(默认),并且 concurrencyPolicy
设置为 Allow
,则作业将始终至少运行一次。
如果 startingDeadlineSeconds 的设置值低于 10 秒钟,CronJob 可能无法被调度。 这是因为 CronJob 控制器每 10 秒钟执行一次检查。
# 服务网络
Kubernetes 网络解决四方面的问题:
- 一个 Pod 中的容器之间通过本地回路(loopback)通信 (opens new window)。
- 集群网络在不同 Pod 之间提供通信。
- Service (opens new window) API 允许你向外暴露 Pod 中运行的应用 (opens new window), 以支持来自于集群外部的访问。
- Ingress (opens new window) 提供专门用于暴露 HTTP 应用程序、网站和 API 的额外功能。
- 你也可以使用 Service 来发布仅供集群内部使用的服务 (opens new window)。
# 服务类型
对一些应用的某些部分(如前端),可能希望将其暴露给 Kubernetes 集群外部的 IP 地址。 Kubernetes ServiceTypes 允许指定你所需要的 Service 类型。 Type 的取值以及行为如下:
- ClusterIP:通过集群的内部 IP 暴露服务,选择该值时服务只能够在集群内部访问。 这也是你没有为服务显式指定 type 时使用的默认值。
- NodePort (opens new window):通过每个节点上的 IP 和静态端口(NodePort)暴露服务。 为了让节点端口可用,Kubernetes 设置了集群 IP 地址,这等同于你请求 type: ClusterIP 的服务。
- LoadBalancer (opens new window):使用云提供商的负载均衡器向外部暴露服务。 外部负载均衡器可以将流量路由到自动创建的 NodePort 服务和 ClusterIP 服务上。
- ExternalName (opens new window):通过返回 CNAME 记录和对应值,可以将服务映射到 externalName 字段的内容(例如,foo.bar.example.com)。 无需创建任何类型代理。
# 无Selector的Service
- 我们可以创建Service不指定Selector
- 然后手动创建EndPoint,指定一组Pod地址。
- 此场景用于我们负载均衡其他中间件场景。
apiVersion: v1
kind: Service
metadata:
name: endpoints-name
namespace: default
spec:
ports:
- protocol: TCP
port: 80
name: http
targetPort: 80
---
apiVersion: v1
kind: Endpoints
metadata:
name: endpoints-name
namespace: default
subsets:
- addresses:
- ip: 192.169.169.151
- ip: 192.169.36.92
- ip: 106.11.248.146
ports:
- port: 80
name: http
protocol: TCP
svc 的 name 要和 ep 的 name 相同,如果 ports 有指定 name,name也要相通 ip 可以写公网地址
# ClusterIP
type: ClusterIP
ClusterIP: 手动指定/None/""
- 手动指定的ClusterIP必须在合法范围内
- None会创建出没有ClusterIP的headless service(无头服务),Pod需要用服务的域名访问
# NodePort
apiVersion: v1
kind: Service
metadata:
name: nodeport-test
namespace: default
spec:
selector:
app: nodeport-test
type: NodePort
ports:
- name: nodeport-test
protocol: TCP
port: 80
targetPort: 80
nodePort: 30001
- 如果将 type 字段设置为 NodePort ,则 Kubernetes 将在 --service-node-port-range 标志指定的范围内分配端口(默认值:30000-32767)
- k8s集群的所有机器都将打开监听这个端口的数据,访问任何一个机器,都可以访问这个service对应的Pod
- 使用 nodePort 自定义端口
# ExternalName
apiVersion: v1
kind: Service
metadata:
name: externalname-test
namespace: default
spec:
type: ExternalName
externalName: aliyun.com
进入 pod,访问 externalname-test 对应 externalName 指定的域名
- externalName 需要域名,不能是 ip
- 注意跨域问题
# LoadBalancer
在使用支持外部负载均衡器的云提供商的服务时,设置 type 的值为 "LoadBalancer", 将为 Service 提供负载均衡器。 负载均衡器是异步创建的,关于被提供的负载均衡器的信息将会通过 Service 的 status.loadBalancer 字段发布出去。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app.kubernetes.io/name: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
clusterIP: 10.0.171.239
type: LoadBalancer
status:
loadBalancer:
ingress:
- ip: 192.0.2.127
# 扩展-externalIPs
externalIPs 是集群中的节点将用于的 IP 地址列表也接受此服务的流量。 这些 IP 不受 Kubernetes 管理。 用户有责任确保流量到达具有此 IP 的节点。一个常见的例子是外部负载平衡器不是 Kubernetes 系统的一部分。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app.kubernetes.io/name: MyApp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
externalIPs:
- 80.11.12.10
# 扩展-pod 的 DNS
当前,创建 Pod 时其主机名取自 Pod 的 metadata.name 值。 Pod 规约中包含一个可选的 hostname 字段,可以用来指定 Pod 的主机名。 当这个字段被设置时,它将优先于 Pod 的名字成为该 Pod 的主机名。 举个例子,给定一个 hostname 设置为 "my-host" 的 Pod, 该 Pod 的主机名将被设置为 "my-host"。 Pod 规约还有一个可选的 subdomain 字段,可以用来指定 Pod 的子域名。 举个例子,某 Pod 的 hostname 设置为 “foo”,subdomain 设置为 “bar”, 在名字空间 “my-namespace” 中对应的完全限定域名(FQDN)为 “foo.bar.my-namespace.svc.cluster-domain.example”。
apiVersion: v1
kind: Service
metadata:
name: default-subdomain
spec:
selector:
name: busybox
clusterIP: None
ports:
- name: foo # 实际上不需要指定端口号
port: 1234
targetPort: 1234
---
apiVersion: v1
kind: Pod
metadata:
name: busybox1
labels:
name: busybox
spec:
hostname: busybox-1
subdomain: default-subdomain
containers:
- image: busybox:1.28
command:
- sleep
- "3600"
name: busybox
---
apiVersion: v1
kind: Pod
metadata:
name: busybox2
labels:
name: busybox
spec:
hostname: busybox-2
subdomain: default-subdomain
containers:
- image: busybox:1.28
command:
- sleep
- "3600"
name: busybox
如果某无头 Service 与某 Pod 在同一个名字空间中,且它们具有相同的子域名(pod 的 subdomain 和 svc 的 name 相同), 集群的 DNS 服务器也会为该 Pod 的全限定主机名返回 A 记录或 AAAA 记录。 例如,在同一个名字空间中,给定一个主机名为 “busybox-1”、 子域名设置为 “default-subdomain” 的 Pod,和一个名称为 “default-subdomain” 的无头 Service,Pod 将看到自己的 FQDN 为 "busybox-1.default-subdomain.my-namespace.svc.cluster-domain.example"。 DNS 会为此名字提供一个 A 记录或 AAAA 记录,指向该 Pod 的 IP。 “busybox1” 和 “busybox2” 这两个 Pod 分别具有它们自己的 A 或 AAAA 记录。
# Ingress
- Ingress (opens new window) 公开从集群外部到集群内服务 (opens new window)的 HTTP 和 HTTPS 路由。 流量路由由 Ingress 资源上定义的规则控制。
- Ingress 可为 Service 提供外部可访问的 URL、负载均衡流量、终止 SSL/TLS,以及基于名称的虚拟托管。 Ingress 控制器 (opens new window) 通常负责通过负载均衡器来实现 Ingress,尽管它也可以配置边缘路由器或其他前端来帮助处理流量。
- Ingress 不会公开任意端口或协议。 将 HTTP 和 HTTPS 以外的服务公开到 Internet 时,通常使用 Service.Type=NodePort (opens new window) 或 Service.Type=LoadBalancer (opens new window) 类型的 Service。
# ingress nginx 安装
你必须拥有一个 Ingress 控制器 (opens new window) 才能满足 Ingress 的要求。 仅创建 Ingress 资源本身没有任何效果。 你可能需要部署 Ingress 控制器,例如 ingress-nginx (opens new window)。 你可以从许多 Ingress 控制器 (opens new window) 中进行选择。 裸机集群安装 https://kubernetes.github.io/ingress-nginx/deploy/#bare-metal-clusters (opens new window)
修改 yaml
NodePort
替换为ClusterIP
- pod 设置为
hostNetwork
template:
spec:
hostNetwork: true
- Deployment 改为
DaemonSet
- dnsPolicy 改为
ClusterFirstWithHostNet
完整配置
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.5.1
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.5.1
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.5.1
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:
- ""
resourceNames:
- ingress-nginx-leader
resources:
- configmaps
verbs:
- get
- update
- apiGroups:
- ""
resources:
- configmaps
verbs:
- create
- 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.5.1
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.5.1
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.5.1
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.5.1
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.5.1
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.5.1
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.5.1
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.5.1
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.5.1
name: ingress-nginx-controller
namespace: ingress-nginx
spec:
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: ClusterIP
---
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.5.1
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: 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.5.1
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
- --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
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.k8s.io/ingress-nginx/controller:v1.5.1
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:
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.5.1
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.5.1
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.k8s.io/ingress-nginx/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.5.1
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.5.1
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.k8s.io/ingress-nginx/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.5.1
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.5.1
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
访问 node 的 80 端口验证
# 使用
# 基础配置
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-nginx
namespace: default
spec:
ingressClassName: nginx
rules:
- host: my-nginx.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-cluster
port:
number: 80
修改 hosts,node 的 ip 对应 my-nginx.com 测试
curl my-nginx.com
# 默认后端
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-nginx-default
namespace: default
spec:
ingressClassName: nginx
defaultBackend:
service:
name: php-apache
port:
number: 80
rules:
- host: my-nginx-default.com
http:
paths:
- path: /aaa
pathType: Prefix
backend:
service:
name: nginx-cluster
port:
number: 80
- my-nginx-default.com 域名下的非 /aaa 请求会到 defaultBackend
- 非 my-nginx-default.com 域名下的请求也会到 defaultBackend
- rules 不匹配的都会到 defaultBackend
# 路径重写
https://kubernetes.github.io/ingress-nginx/examples/rewrite/ (opens new window)
Rewrite 功能,经常被用于前后分离的场景
- 前端给服务器发送 / 请求映射前端地址。
- 后端给服务器发送 /api 请求来到对应的服务。但是后端服务没有 /api的起始路径,所以需要 ingress-controller 自动截串
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
name: rewirte
namespace: default
spec:
ingressClassName: nginx
rules:
- host: test.com
http:
paths:
- path: /api(/|$)(.*)
pathType: Prefix
backend:
service:
name: php-apache
port:
number: 80
- test.com/api => test.com/
- test.com/api/ => test.com/
- test.com/api/new => test.com/new
# 配置 SSL
https://kubernetes.github.io/ingress-nginx/user-guide/tls/ (opens new window) 生成自签名证书和私钥
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout test.key -out test.cert -subj "/CN=test.com/O=test.com"
创建密钥
kubectl create secret tls test-tls --key test.key --cert test.cert
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: test-tls
namespace: default
spec:
ingressClassName: nginx
tls:
- hosts:
- test.com
secretName: test-tls
rules:
- host: test.com
http:
paths:
- path: /ccc
pathType: Prefix
backend:
service:
name: php-apache
port:
number: 80
# 金丝雀
https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#canary (opens new window) 开启 nginx.ingress.kubernetes.io/canary: "true":
- nginx.ingress.kubernetes.io/canary-by-header:用于通知 Ingress 将请求路由到 Canary Ingress 中指定的服务的标头。当请求头设置为always时,它将被路由到金丝雀。当标头设置为 时never,它永远不会被路由到金丝雀。
- nginx.ingress.kubernetes.io/canary-by-header-value:要匹配的标头值,用于通知 Ingress 将请求路由到 Canary Ingress 中指定的服务。当请求标头设置为该值时,它将被路由到金丝雀。此注释必须与 一起使用nginx.ingress.kubernetes.io/canary-by-header。注释是 的扩展,nginx.ingress.kubernetes.io/canary-by-header允许自定义标头值而不是使用硬编码值。nginx.ingress.kubernetes.io/canary-by-header如果未定义注释,则它没有任何效果。
- nginx.ingress.kubernetes.io/canary-by-header-pattern:这与canary-by-header-valuePCRE 正则表达式匹配不同,工作方式相同。请注意,canary-by-header-value设置此注释时将被忽略。
- nginx.ingress.kubernetes.io/canary-by-cookie:用于通知 Ingress 将请求路由到 Canary Ingress 中指定的服务的 cookie。当 cookie 值设置为always时,它将被路由到金丝雀。当 cookie 设置为 时never,它永远不会被路由到金丝雀。
- nginx.ingress.kubernetes.io/canary-weight: 基于整数 (0 -) 应路由到金丝雀 Ingress 中指定的服务的随机请求的百分比。权重为 0 表示此金丝雀规则不会向金丝雀入口中的服务发送任何请求。
- nginx.ingress.kubernetes.io/canary-weight-total:流量的总权重。如果未指定,则默认为 100。
Canary 规则按优先顺序进行评估。优先级如下:canary-by-header -> canary-by-cookie -> canary-weight
部署 2 个 pod,修改 html 页面
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-v1
namespace: default
labels:
app: nginx-v1
spec:
selector:
matchLabels:
app: nginx-v1
replicas: 1
template:
metadata:
labels:
app: nginx-v1
spec:
containers:
- name: nginx-v1
image: nginx:latest
pullImagePolicy: Never
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-v2
namespace: default
labels:
app: nginx-v2
spec:
selector:
matchLabels:
app: nginx-v2
replicas: 1
template:
metadata:
labels:
app: nginx-v2
spec:
containers:
- name: nginx-v2
image: nginx:latest
pullImagePolicy: Never
---
apiVersion: v1
kind: Service
metadata:
name: nginx-v1
namespace: default
spec:
selector:
app: nginx-v1
type: ClusterIP
ports:
- name: nginx-v1
protocol: TCP
port: 80
targetPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-v2
namespace: default
spec:
selector:
app: nginx-v2
type: ClusterIP
ports:
- name: nginx-v2
protocol: TCP
port: 80
targetPort: 80
正常 ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: canary-v1
namespace: default
spec:
ingressClassName: nginx
rules:
- host: test-canary.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-v1
port:
number: 80
金丝雀 ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: canary-v2
namespace: default
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-header: "key"
spec:
ingressClassName: nginx
rules:
- host: test-canary.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-v2
port:
number: 80
# 总是路由到新版本
curl -H "key:always" test-canary.com
# 不路由到新版本
curl -H "key:never" test-canary.com
# 网络策略
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: test-network-policy
namespace: default
spec:
podSelector:
matchLabels:
role: db
policyTypes:
- Ingress
- Egress
ingress:
- from:
- ipBlock:
cidr: 172.17.0.0/16
except:
- 172.17.1.0/24
- namespaceSelector:
matchLabels:
project: myproject
- podSelector:
matchLabels:
role: frontend
ports:
- protocol: TCP
port: 6379
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/24
ports:
- protocol: TCP
port: 5978
spec:NetworkPolicy 规约 中包含了在一个名字空间中定义特定网络策略所需的所有信息。
- podSelector:每个 NetworkPolicy 都包括一个 podSelector, 它对该策略所适用的一组 Pod 进行选择。示例中的策略选择带有 "role=db" 标签的 Pod。 空的 podSelector 选择名字空间下的所有 Pod。
- policyTypes:每个 NetworkPolicy 都包含一个 policyTypes 列表,其中包含 Ingress 或 Egress 或两者兼具。policyTypes 字段表示给定的策略是应用于进入所选 Pod 的入站流量还是来自所选 Pod 的出站流量,或两者兼有。 如果 NetworkPolicy 未指定 policyTypes 则默认情况下始终设置 Ingress; 如果 NetworkPolicy 有任何出口规则的话则设置 Egress。
- ingress:每个 NetworkPolicy 可包含一个 ingress 规则的白名单列表。 每个规则都允许同时匹配 from 和 ports 部分的流量。示例策略中包含一条简单的规则: 它匹配某个特定端口,来自三个来源中的一个,第一个通过 ipBlock 指定,第二个通过 namespaceSelector 指定,第三个通过 podSelector 指定。
- egress:每个 NetworkPolicy 可包含一个 egress 规则的白名单列表。 每个规则都允许匹配 to 和 port 部分的流量。该示例策略包含一条规则, 该规则将指定端口上的流量匹配到 10.0.0.0/24 中的任何目的地。
所以,该网络策略示例:
- 隔离 default 名字空间下 role=db 的 Pod (如果它们不是已经被隔离的话)。
- (Ingress 规则)允许以下 Pod 连接到 default 名字空间下的带有 role=db 标签的所有 Pod 的 6379 TCP 端口:
- default 名字空间下带有 role=frontend 标签的所有 Pod
- 带有 project=myproject 标签的所有名字空间中的 Pod
- IP 地址范围为 172.17.0.0–172.17.0.255 和 172.17.2.0–172.17.255.255 (即,除了 172.17.1.0/24 之外的所有 172.17.0.0/16)
- (Egress 规则)允许 default 名字空间中任何带有标签 role=db 的 Pod 到 CIDR 10.0.0.0/24 下 5978 TCP 端口的连接。
# 配置
# Secret
Secret 是一种包含少量敏感信息例如密码、令牌或密钥的对象。 这样的信息可能会被放在 Pod (opens new window) 规约中或者镜像中。 使用 Secret 意味着你不需要在应用程序代码中包含机密数据。 由于创建 Secret 可以独立于使用它们的 Pod, 因此在创建、查看和编辑 Pod 的工作流程中暴露 Secret(及其数据)的风险较小。 Kubernetes 和在集群中运行的应用程序也可以对 Secret 采取额外的预防措施, 例如避免将机密数据写入非易失性存储。
# Secret 的类型
创建 Secret 时,你可以使用 Secret (opens new window) 资源的 type 字段,或者与其等价的 kubectl 命令行参数(如果有的话)为其设置类型。 Secret 类型有助于对 Secret 数据进行编程处理。 Kubernetes 提供若干种内置的类型,用于一些常见的使用场景。 针对这些类型,Kubernetes 所执行的合法性检查操作以及对其所实施的限制各不相同。
内置类型 | 用法 |
---|---|
Opaque | 用户定义的任意数据 |
kubernetes.io/service-account-token | 服务账号令牌 |
kubernetes.io/dockercfg | ~/.dockercfg 文件的序列化形式 |
kubernetes.io/dockerconfigjson | ~/.docker/config.json 文件的序列化形式 |
kubernetes.io/basic-auth | 用于基本身份认证的凭据 |
kubernetes.io/ssh-auth | 用于 SSH 身份认证的凭据 |
kubernetes.io/tls | 用于 TLS 客户端或者服务器端的数据 |
bootstrap.kubernetes.io/token | 启动引导令牌数据 |
通过为 Secret 对象的 type 字段设置一个非空的字符串值,你也可以定义并使用自己 Secret 类型(如果 type 值为空字符串,则被视为 Opaque 类型)。
# Pod 中使用 Secret
如果你希望在 Pod 中访问 Secret 内的数据,一种方式是让 Kubernetes 将 Secret 以 Pod 中一个或多个容器的文件系统中的文件的形式呈现出来。 要配置这种行为,你需要:
- 创建一个 Secret 或者使用已有的 Secret。多个 Pod 可以引用同一个 Secret。
- 更改 Pod 定义,在 .spec.volumes[] 下添加一个卷。根据需要为卷设置其名称, 并将 .spec.volumes[].secret.secretName 字段设置为 Secret 对象的名称。
- 为每个需要该 Secret 的容器添加 .spec.containers[].volumeMounts[]。 并将 .spec.containers[].volumeMounts[].readOnly 设置为 true, 将 .spec.containers[].volumeMounts[].mountPath 设置为希望 Secret 被放置的、目前尚未被使用的路径名。
- 更改你的镜像或命令行,以便程序读取所设置的目录下的文件。Secret 的 data 映射中的每个主键都成为 mountPath 下面的文件名。
# 创建 Secret
命令行创建
# 本文输入
kubectl create secret generic dev-db-secret --from-literal=username=devuser --from-literal=password='S!B\*d$zDsb='
# 文件输入
k create secret generic db-user-pass --from-file=username=username.txt --from-file=pwd=password.txt
yaml 创建 data 需要 base64 编码
apiVersion: v1
kind: Secret
metadata:
name: mysecret
namespace: default
type: Opaque
data:
username: cm9vdA==
password: MTIzNDU2
# 使用 Secret
apiVersion: v1
kind: Pod
metadata:
name: "secret-pod-001"
namespace: default
labels:
app: "secret-pod-001"
spec:
containers:
- name: secret-pod-001
image: "nginx:latest"
imagePullPolicy: Never
volumeMounts:
- name: db-volume
mountPath: /app
readOnly: true
env:
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-user-pass
key: username
- name: DB_PWD
valueFrom:
secretKeyRef:
name: db-user-pass
key: pwd
volumes:
- name: db-volume
secret:
secretName: db-user-pass
items:
- key: username
path: db-user
- key: pwd
path: db-pwd
- 环境变量中可以引用 secret,可以将 secret 挂载到 pod 中
- items 可以指定要挂载的 key,path 指定文件路径,相对路径。/app/db-user 文件对应 username 的值
- 环境变量引用的方式不会被自动更新
- 挂载方式的secret 在secret变化的时候会自动更新(子路径引用除外)
# ConfigMap
ConfigMap 是一种 API 对象,用来将非机密性的数据保存到键值对中。使用时, Pods (opens new window) 可以将其用作环境变量、命令行参数或者存储卷中的配置文件。 ConfigMap 将你的环境配置信息和 容器镜像 (opens new window) 解耦,便于应用配置的修改。
你可以使用四种方式来使用 ConfigMap 配置 Pod 中的容器:
- 在容器命令和参数内
- 容器的环境变量
- 在只读卷里面添加一个文件,让应用来读取
- 编写代码在 Pod 中运行,使用 Kubernetes API 来读取 ConfigMap
# 创建 ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: game-demo
data:
# 类属性键;每一个键都映射到一个简单的值
player_initial_lives: "3"
ui_properties_file_name: "user-interface.properties"
# 类文件键
game.properties: |
enemy.types=aliens,monsters
player.maximum-lives=5
user-interface.properties: |
color.good=purple
color.bad=yellow
allow.textmode=true
# 使用 ConfigMap
apiVersion: v1
kind: Pod
metadata:
name: configmap-demo-pod
spec:
containers:
- name: demo
image: nginx:latest
imagePullPolicy: Never
command: ["sleep", "3600"]
env:
# 定义环境变量
- name: PLAYER_INITIAL_LIVES # 请注意这里和 ConfigMap 中的键名是不一样的
valueFrom:
configMapKeyRef:
name: game-demo # 这个值来自 ConfigMap
key: player_initial_lives # 需要取值的键
- name: UI_PROPERTIES_FILE_NAME
valueFrom:
configMapKeyRef:
name: game-demo
key: ui_properties_file_name
volumeMounts:
- name: config
mountPath: "/config"
readOnly: true
volumes:
# 你可以在 Pod 级别设置卷,然后将其挂载到 Pod 内的容器中
- name: config
configMap:
# 提供你想要挂载的 ConfigMap 的名字
name: game-demo
# 来自 ConfigMap 的一组键,将被创建为文件
items:
- key: "game.properties"
path: "game.properties"
- key: "user-interface.properties"
path: "user-interface.properties"
# 存储
# 临时卷
# 临时卷的类型
Kubernetes 为了不同的用途,支持几种不同类型的临时卷:
- emptyDir (opens new window): Pod 启动时为空,存储空间来自本地的 kubelet 根目录(通常是根磁盘)或内存
- configMap (opens new window)、 downwardAPI (opens new window)、 secret (opens new window): 将不同类型的 Kubernetes 数据注入到 Pod 中
- CSI 临时卷 (opens new window): 类似于前面的卷类型,但由专门支持此特性 (opens new window) 的指定 CSI 驱动程序 (opens new window)提供
- 通用临时卷 (opens new window): 它可以由所有支持持久卷的存储驱动程序提供
emptyDir、configMap、downwardAPI、secret 是作为 本地临时存储 (opens new window) 提供的。它们由各个节点上的 kubelet 管理。
# emptyDir
当 Pod 分派到某个节点上时,emptyDir 卷会被创建,并且在 Pod 在该节点上运行期间,卷一直存在。 就像其名称表示的那样,卷最初是空的。 尽管 Pod 中的容器挂载 emptyDir 卷的路径可能相同也可能不同,这些容器都可以读写 emptyDir 卷中相同的文件。 当 Pod 因为某些原因被从节点上删除时,emptyDir 卷中的数据也会被永久删除。
容器崩溃并不会导致 Pod 被从节点上移除,因此容器崩溃期间 emptyDir 卷中的数据是安全的。
apiVersion: v1
kind: Pod
metadata:
name: "pod-emptydir"
namespace: default
labels:
app: "pod-emptydir"
spec:
containers:
- name: pod-emptydir
image: "nginx:latest"
imagePullPolicy: Never
volumeMounts:
- name: nginx-vol
mountPath: /app
volumes:
- name: nginx-vol
emptyDir: {}
emptyDir: {} 匿名挂载
# hostPath
HostPath 卷存在许多安全风险,最佳做法是尽可能避免使用 HostPath。 当必须使用 HostPath 卷时,它的范围应仅限于所需的文件或目录,并以只读方式挂载。 如果通过 AdmissionPolicy 限制 HostPath 对特定目录的访问,则必须要求 volumeMounts 使用 readOnly 挂载以使策略生效。
hostPath 卷能将主机节点文件系统上的文件或目录挂载到你的 Pod 中。 虽然这不是大多数 Pod 需要的,但是它为一些应用程序提供了强大的逃生舱。 例如,hostPath 的一些用法有:
- 运行一个需要访问 Docker 内部机制的容器;可使用 hostPath 挂载 /var/lib/docker 路径。
- 在容器中运行 cAdvisor 时,以 hostPath 方式挂载 /sys。
- 允许 Pod 指定给定的 hostPath 在运行 Pod 之前是否应该存在,是否应该创建以及应该以什么方式存在。
除了必需的 path 属性之外,你可以选择性地为 hostPath 卷指定 type。
取值 | 行为 |
---|---|
空字符串(默认)用于向后兼容,这意味着在安装 hostPath 卷之前不会执行任何检查。 | |
DirectoryOrCreate | 如果在给定路径上什么都不存在,那么将根据需要创建空目录,权限设置为 0755,具有与 kubelet 相同的组和属主信息。 |
Directory | 在给定路径上必须存在的目录。 |
FileOrCreate | 如果在给定路径上什么都不存在,那么将在那里根据需要创建空文件,权限设置为 0644,具有与 kubelet 相同的组和所有权。 |
File | 在给定路径上必须存在的文件。 |
Socket | 在给定路径上必须存在的 UNIX 套接字。 |
CharDevice | 在给定路径上必须存在的字符设备。 |
BlockDevice | 在给定路径上必须存在的块设备。 |
apiVersion: v1
kind: Pod
metadata:
name: "pod-hostpath"
namespace: default
labels:
app: "pod-hostpath"
spec:
containers:
- name: pod-hostpath
image: "nginx:latest"
imagePullPolicy: Never
volumeMounts:
- name: nginx-vol
mountPath: /app
volumes:
- name: nginx-vol
hostPath:
path: /data
# 持久卷
# NFS
server 配置
# 在任意机器
yum install -y nfs-utils
#执行命令 vi /etc/exports,创建 exports 文件,文件内容如下:
echo "/nfs/data/ *(insecure,rw,sync,no_root_squash)" > /etc/exports
#/nfs/data 172.26.248.0/20(rw,no_root_squash)
# 执行以下命令,启动 nfs 服务;创建共享目录
mkdir -p /nfs/data
systemctl enable rpcbind
systemctl enable nfs-server
systemctl start rpcbind
systemctl start nfs-server
exportfs -r
#检查配置是否生效
exportfs
# 输出结果如下所示
/nfs/data /nfs/data
node 配置
#服务器端防火墙开放111、662、875、892、2049的 tcp / udp 允许,否则远端客户无法连接。
#安装客户端工具
yum install -y nfs-utils
#执行以下命令检查 nfs 服务器端是否有设置共享目录
# showmount -e $(nfs服务器的IP)
showmount -e 172.26.165.243
# 输出结果如下所示
Export list for 172.26.165.243
/nfs/data *
#执行以下命令挂载 nfs 服务器上的共享目录到本机路径 /root/nfsmount
mkdir /root/nfsmount
# mount -t nfs $(nfs服务器的IP):/root/nfs_root /root/nfsmount
#高可用备份的方式
mount -t nfs 172.26.165.243:/nfs/data /root/nfsmount
# 写入一个测试文件
echo "hello nfs server" > /root/nfsmount/test.txt
#在 nfs 服务器上执行以下命令,验证文件写入成功
cat /root/nfsmount/test.txt
使用 nfs
apiVersion: v1
kind: Pod
metadata:
name: "pod-nfs"
namespace: default
labels:
app: "pod-nfs"
spec:
containers:
- name: pod-nfs
image: "nginx:latest"
imagePullPolicy: Never
volumeMounts:
- name: localtime
mountPath: /etc/localtime
- name: nginx-vol
mountPath: /app
volumes:
- name: localtime
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
- name: nginx-vol
nfs:
server: 192.168.83.133
path: /nfs/data/
# 概念
持久卷(PersistentVolume,PV) 是集群中的一块存储,可以由管理员事先制备, 或者使用存储类(Storage Class) (opens new window)来动态制备。
- 持久卷是集群资源,就像节点也是集群资源一样。
- PV 持久卷和普通的 Volume 一样, 也是使用卷插件来实现的,只是它们拥有独立于任何使用 PV 的 Pod 的生命周期。
- 此 API 对象中记述了存储的实现细节,无论其背后是 NFS、iSCSI 还是特定于云平台的存储系统。
持久卷申领(PersistentVolumeClaim,PVC) 表达的是用户对存储的请求。概念上与 Pod 类似。
- Pod 会耗用节点资源,而 PVC 申领会耗用 PV 资源。
- Pod 可以请求特定数量的资源(CPU 和内存);
- 同样 PVC 申领也可以请求特定的大小和访问模式 (例如,可以要求 PV 卷能够以 ReadWriteOnce、ReadOnlyMany 或 ReadWriteMany 模式之一来挂载,参见访问模式 (opens new window))。
尽管 PersistentVolumeClaim 允许用户消耗抽象的存储资源, 常见的情况是针对不同的问题用户需要的是具有不同属性(如,性能)的 PersistentVolume 卷。 集群管理员需要能够提供不同性质的 PersistentVolume, 并且这些 PV 卷之间的差别不仅限于卷大小和访问模式,同时又不能将卷是如何实现的这些细节暴露给用户。 为了满足这类需求,就有了存储类(StorageClass) 资源。
# 使用
apiVersion: v1
kind: PersistentVolume
metadata:
name: task-pv-volume
spec:
storageClassName: my-nfs-volume
capacity:
storage: 50Mi
accessModes:
- ReadWriteOnce
nfs:
path: "/nfs/data"
server: 192.168.83.133
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: task-pv-claim
spec:
storageClassName: my-nfs-volume
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 40Mi
---
apiVersion: v1
kind: Pod
metadata:
name: "task-pv-pod"
spec:
containers:
- name: task-pv-pod
image: "nginx:latest"
imagePullPolicy: Never
volumeMounts:
- name: nginx-vol
mountPath: /app
volumes:
- name: nginx-vol
persistentVolumeClaim:
claimName: task-pv-claim
# 访问模式
- **ReadWriteOnce **卷可以被一个节点以读写方式挂载。 ReadWriteOnce 访问模式也允许运行在同一节点上的多个 Pod 访问卷。
- **ReadOnlyMany **卷可以被多个节点以只读方式挂载。
- **ReadWriteMany **卷可以被多个节点以读写方式挂载。
- **ReadWriteOncePod **卷可以被单个 Pod 以读写方式挂载。 如果你想确保整个集群中只有一个 Pod 可以读取或写入该 PVC, 请使用 ReadWriteOncePod 访问模式。这只支持 CSI 卷以及需要 Kubernetes 1.22 以上版本。
在命令行接口(CLI)中,访问模式也使用以下缩写形式:
- RWO - ReadWriteOnce
- ROX - ReadOnlyMany
- RWX - ReadWriteMany
- RWOP - ReadWriteOncePod
# 阶段
每个卷会处于以下阶段(Phase)之一:
- Available(可用)-- 卷是一个空闲资源,尚未绑定到任何申领;
- Bound(已绑定)-- 该卷已经绑定到某申领;
- Released(已释放)-- 所绑定的申领已被删除,但是资源尚未被集群回收;
- Failed(失败)-- 卷的自动回收操作失败。
# 回收策略
目前的回收策略有:
- Retain -- 手动回收
- Recycle -- 基本擦除 (rm -rf /thevolume/*)
- Delete -- 诸如 AWS EBS、GCE PD、Azure Disk 或 OpenStack Cinder 卷这类关联存储资产也被删除
目前,仅 NFS 和 HostPath 支持回收(Recycle)。 AWS EBS、GCE PD、Azure Disk 和 Cinder 卷都支持删除(Delete)。
# 动态卷制备
动态卷制备允许按需创建存储卷。 如果没有动态制备,集群管理员必须手动地联系他们的云或存储提供商来创建新的存储卷, 然后在 Kubernetes 集群创建 PersistentVolume对象 (opens new window)来表示这些卷。 动态制备功能消除了集群管理员预先配置存储的需要。相反,它在用户请求时自动制备存储。
动态卷制备的实现基于 storage.k8s.io API 组中的 StorageClass API 对象。 集群管理员可以根据需要定义多个 StorageClass 对象,每个对象指定一个卷插件(又名 provisioner), 卷插件向卷制备商提供在创建卷时需要的数据卷信息及相关参数。 集群管理员可以在集群中定义和公开多种存储(来自相同或不同的存储系统),每种都具有自定义参数集。 该设计也确保终端用户不必担心存储制备的复杂性和细微差别,但仍然能够从多个存储选项中进行选择。 点击这里 (opens new window)查阅有关存储类的更多信息。
# nfs 动态卷
nfs 插件 https://kubernetes.io/zh-cn/docs/concepts/storage/storage-classes/#nfs (opens new window) 部署文件 https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner/tree/master/deploy (opens new window)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-client
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # or choose another name, must match deployment's env PROVISIONER_NAME'
parameters:
archiveOnDelete: "false"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
labels:
app: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: k8s.gcr.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: k8s-sigs.io/nfs-subdir-external-provisioner
- name: NFS_SERVER
value: 192.168.83.133
- name: NFS_PATH
value: /nfs/data
volumes:
- name: nfs-client-root
nfs:
server: 192.168.83.133
path: /nfs/data
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
rules:
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io
测试 pvc
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: test-claim
spec:
storageClassName: nfs-client
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Mi
声明 pvc 会自动创建 pv,关联到 pvc
[root@k8s-master data]# ll /nfs/data
total 4
drwxrwxrwx 2 root root 6 Jan 30 02:58 default-test-claim-pvc-acc42deb-d2b1-46bc-8be5-3c57ebc8aa46
-rw-r--r-- 1 root root 4 Jan 20 06:46 test.txt
[root@k8s-master data]# k get pv,pvc
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pvc-acc42deb-d2b1-46bc-8be5-3c57ebc8aa46 1Mi RWX Delete Bound default/test-claim nfs-client 3m59s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/test-claim Bound pvc-acc42deb-d2b1-46bc-8be5-3c57ebc8aa46 1Mi RWX nfs-client 3m59s
# 默认 StorageClass
https://kubernetes.io/zh-cn/docs/tasks/administer-cluster/change-default-storage-class/ (opens new window) 添加注解,pvc 不指定 storageClassName 既使用默认的 sc
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
annotations:
storageclass.kubernetes.io/is-default-class: "true"
name: nfs-client-default
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
parameters:
archiveOnDelete: "true"
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: test-claim-default
spec:
# storageClassName: nfs-client-default
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Mi
---
kind: Pod
apiVersion: v1
metadata:
name: test-pod-default
spec:
containers:
- name: test-pod
image: nginx:latest
imagePullPolicy: Never
volumeMounts:
- name: nfs-pvc
mountPath: "/usr/share/nginx/html/"
restartPolicy: "Never"
volumes:
- name: nfs-pvc
persistentVolumeClaim:
claimName: test-claim-default
# 策略
# 限制范围
LimitRange 是限制命名空间内可为每个适用的对象类别 (例如 Pod 或 PersistentVolumeClaim (opens new window)) 指定的资源分配量(限制和请求)的策略对象。 一个 LimitRange(限制范围) 对象提供的限制能够做到:
- 在一个命名空间中实施对每个 Pod 或 Container 最小和最大的资源使用量的限制。
- 在一个命名空间中实施对每个 PersistentVolumeClaim (opens new window) 能申请的最小和最大的存储空间大小的限制。
- 在一个命名空间中实施对一种资源的申请值和限制值的比值的控制。
- 设置一个命名空间中对计算资源的默认申请/限制值,并且自动的在运行时注入到多个 Container 中。
当某命名空间中有一个 LimitRange 对象时,将在该命名空间中实施 LimitRange 限制。
# LimitRange for Pod or Container
apiVersion: v1
kind: LimitRange
metadata:
name: limitrange-container
namespace: test
spec:
limits:
- type: Container
default:
cpu: 100m
memory: 512Mi
defaultRequest:
cpu: 100m
memory: 256Mi
max:
cpu: 200m
memory: 512Mi
min:
cpu: 100m
memory: 256Mi
---
# LimitRange for PersistentVolumeClaim
apiVersion: v1
kind: LimitRange
metadata:
name: limitrange-pvc
namespace: test
spec:
limits:
- type: PersistentVolumeClaim
default:
storage: 2Gi
defaultRequest:
storage: 2Gi
max:
storage: 2Gi
min:
storage: 2Gi
# 资源配额
资源配额,通过 ResourceQuota 对象来定义,对每个命名空间的资源消耗总量提供限制。 它可以限制命名空间中某种类型的对象的总数目上限,也可以限制命名空间中的 Pod 可以使用的计算资源的总上限。 资源配额的工作方式如下:
- 不同的团队可以在不同的命名空间下工作。这可以通过 RBAC (opens new window) 强制执行。
- 集群管理员可以为每个命名空间创建一个或多个 ResourceQuota 对象。
- 当用户在命名空间下创建资源(如 Pod、Service 等)时,Kubernetes 的配额系统会跟踪集群的资源使用情况, 以确保使用的资源用量不超过 ResourceQuota 中定义的硬性资源限额。
- 如果资源创建或者更新请求违反了配额约束,那么该请求会报错(HTTP 403 FORBIDDEN), 并在消息中给出有可能违反的约束。
- 如果命名空间下的计算资源 (如 cpu 和 memory)的配额被启用, 则用户必须为这些资源设定请求值(request)和约束值(limit),否则配额系统将拒绝 Pod 的创建。 提示: 可使用 LimitRanger 准入控制器来为没有设置计算资源需求的 Pod 设置默认值。
https://kubernetes.io/zh-cn/docs/concepts/policy/resource-quotas/ (opens new window)
apiVersion: v1
kind: ResourceQuota
metadata:
name: quota-name
namespace: test
spec:
hard:
requests.cpu: '1'
requests.memory: 1Gi
limits.cpu: '2'
limits.memory: 2Gi
pods: '2'
persistentvolumeclaims: '5'
requests.storage: 5Gi
# 调度
# nodeSelector
nodeSelector 是节点选择约束的最简单推荐形式。你可以将 nodeSelector 字段添加到 Pod 的规约中设置你希望目标节点所具有的节点标签 (opens new window)。 Kubernetes 只会将 Pod 调度到拥有你所指定的每个标签的节点上。 此 Pod 配置文件描述了一个拥有节点选择器 disktype: ssd 的 Pod。这表明该 Pod 将被调度到有 disktype=ssd 标签的节点。
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
nodeSelector:
disktype: ssd
# 节点亲和性
节点亲和性概念上类似于 nodeSelector, 它使你可以根据节点上的标签来约束 Pod 可以调度到哪些节点上。 节点亲和性有两种:
- requiredDuringSchedulingIgnoredDuringExecution: 调度器只有在规则被满足的时候才能执行调度。此功能类似于 nodeSelector, 但其语法表达能力更强。
- preferredDuringSchedulingIgnoredDuringExecution: 调度器会尝试寻找满足对应规则的节点。如果找不到匹配的节点,调度器仍然会调度该 Pod。
在上述类型中,IgnoredDuringExecution 意味着如果节点标签在 Kubernetes 调度 Pod 后发生了变更,Pod 仍将继续运行。
apiVersion: v1
kind: Pod
metadata:
name: with-affinity-anti-affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: label-1
operator: In
values:
- key-1
- weight: 50
preference:
matchExpressions:
- key: label-2
operator: In
values:
- key-2
containers:
- name: with-node-affinity
image: registry.k8s.io/pause:2.0
# Pod 间亲和性
与节点亲和性类似,Pod 的亲和性与反亲和性也有两种类型:
- requiredDuringSchedulingIgnoredDuringExecution
- preferredDuringSchedulingIgnoredDuringExecution
例如,你可以使用 requiredDuringSchedulingIgnoredDuringExecution 亲和性来告诉调度器, 将两个服务的 Pod 放到同一个云提供商可用区内,因为它们彼此之间通信非常频繁。 类似地,你可以使用 preferredDuringSchedulingIgnoredDuringExecution 反亲和性来将同一服务的多个 Pod 分布到多个云提供商可用区中。 要使用 Pod 间亲和性,可以使用 Pod 规约中的 .affinity.podAffinity 字段。 对于 Pod 间反亲和性,可以使用 Pod 规约中的 .affinity.podAntiAffinity 字段。
Pod 间亲和性和反亲和性都需要相当的计算量,因此会在大规模集群中显著降低调度速度。 我们不建议在包含数百个节点的集群中使用这类设置。
# nodeName
nodeName 是比亲和性或者 nodeSelector 更为直接的形式。nodeName 是 Pod 规约中的一个字段。如果 nodeName 字段不为空,调度器会忽略该 Pod, 而指定节点上的 kubelet 会尝试将 Pod 放到该节点上。 使用 nodeName 规则的优先级会高于使用 nodeSelector 或亲和性与非亲和性的规则。
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
nodeName: node1 # 调度 Pod 到特定的节点
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
# Pod 拓扑分布约束
你可以使用 拓扑分布约束(Topology Spread Constraints) 来控制 Pod (opens new window) 在集群内故障域之间的分布, 例如区域(Region)、可用区(Zone)、节点和其他用户自定义拓扑域。 这样做有助于实现高可用并提升资源利用率。 你可以将集群级约束 (opens new window)设为默认值,或为个别工作负载配置拓扑分布约束。
---
apiVersion: v1
kind: Pod
metadata:
name: example-pod
spec:
# 配置一个拓扑分布约束
topologySpreadConstraints:
- maxSkew: <integer>
minDomains: <integer> # 可选;自从 v1.25 开始成为 Beta
topologyKey: <string>
whenUnsatisfiable: <string>
labelSelector: <object>
matchLabelKeys: <list> # 可选;自从 v1.25 开始成为 Alpha
nodeAffinityPolicy: [Honor|Ignore] # 可选;自从 v1.26 开始成为 Beta
nodeTaintsPolicy: [Honor|Ignore] # 可选;自从 v1.26 开始成为 Beta
### 其他 Pod 字段置于此处
- maxSkew 描述这些 Pod 可能被均匀分布的程度。最大倾斜
- topologyKey 是节点标签 (opens new window)的键。如果节点使用此键标记并且具有相同的标签值, 则将这些节点视为处于同一拓扑域中。我们将拓扑域中(即键值对)的每个实例称为一个域。 调度器将尝试在每个拓扑域中放置数量均衡的 Pod。 另外,我们将符合条件的域定义为其节点满足 nodeAffinityPolicy 和 nodeTaintsPolicy 要求的域。
- whenUnsatisfiable 指示如果 Pod 不满足分布约束时如何处理:
- DoNotSchedule(默认)告诉调度器不要调度。
- ScheduleAnyway 告诉调度器仍然继续调度,只是根据如何能将偏差最小化来对节点进行排序。
- labelSelector 用于查找匹配的 Pod。匹配此标签的 Pod 将被统计,以确定相应拓扑域中 Pod 的数量。 有关详细信息,请参考标签选择算符 (opens new window)。
# 污点和容忍度
节点亲和性 (opens new window) 是 Pod (opens new window) 的一种属性,它使 Pod 被吸引到一类特定的节点 (opens new window) (这可能出于一种偏好,也可能是硬性要求)。 污点(Taint) 则相反——它使节点能够排斥一类特定的 Pod。 容忍度(Toleration) 是应用于 Pod 上的。容忍度允许调度器调度带有对应污点的 Pod。 容忍度允许调度但并不保证调度:作为其功能的一部分, 调度器也会评估其他参数 (opens new window)。 污点和容忍度(Toleration)相互配合,可以用来避免 Pod 被分配到不合适的节点上。 每个节点上都可以应用一个或多个污点,这表示对于那些不能容忍这些污点的 Pod, 是不会被该节点接受的。
# 添加污点
kubectl taint nodes node1 key1=value1:NoSchedule
# 去除污点
kubectl taint nodes node1 key1=value1:NoSchedule-
带有容忍的 pod
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
tolerations:
- key: "example-key"
operator: "Exists"
effect: "NoSchedule"
- 如果一个容忍度的 key 为空且 operator 为 Exists, 表示这个容忍度与任意的 key、value 和 effect 都匹配,即这个容忍度能容忍任何污点。
- 如果 effect 为空,则可以与所有键名 key1 的效果相匹配。
当某种条件为真时,节点控制器会自动给节点添加一个污点。当前内置的污点包括:
- node.kubernetes.io/not-ready:节点未准备好。这相当于节点状况 Ready 的值为 "False"。
- node.kubernetes.io/unreachable:节点控制器访问不到节点. 这相当于节点状况 Ready 的值为 "Unknown"。
- node.kubernetes.io/memory-pressure:节点存在内存压力。
- node.kubernetes.io/disk-pressure:节点存在磁盘压力。
- node.kubernetes.io/pid-pressure: 节点的 PID 压力。
- node.kubernetes.io/network-unavailable:节点网络不可用。
- node.kubernetes.io/unschedulable: 节点不可调度。
- node.cloudprovider.kubernetes.io/uninitialized:如果 kubelet 启动时指定了一个“外部”云平台驱动, 它将给当前节点添加一个污点将其标志为不可用。在 cloud-controller-manager 的一个控制器初始化这个节点后,kubelet 将删除这个污点。
在节点被驱逐时,节点控制器或者 kubelet 会添加带有 NoExecute 效果的相关污点。 如果异常状态恢复正常,kubelet 或节点控制器能够移除相关的污点。
# 安全
# Kubernetes API 访问控制
用户使用 kubectl、客户端库或构造 REST 请求来访问 Kubernetes API (opens new window)。 人类用户和 Kubernetes 服务账户 (opens new window)都可以被鉴权访问 API。 当请求到达 API 时,它会经历多个阶段,如下图所示:
# 传输安全
默认情况下,Kubernetes API 服务器在第一个非 localhost 网络接口的 6443 端口上进行监听, 受 TLS 保护。在一个典型的 Kubernetes 生产集群中,API 使用 443 端口。 该端口可以通过 --secure-port 进行变更,监听 IP 地址可以通过 --bind-address 标志进行变更。 API 服务器出示证书。该证书可以使用私有证书颁发机构(CA)签名,也可以基于链接到公认的 CA 的公钥基础架构签名。 该证书和相应的私钥可以通过使用 --tls-cert-file 和 --tls-private-key-file 标志进行设置。 如果你的集群使用私有证书颁发机构,你需要在客户端的 ~/.kube/config 文件中提供该 CA 证书的副本, 以便你可以信任该连接并确认该连接没有被拦截。 你的客户端可以在此阶段出示 TLS 客户端证书。
# 认证
如上图步骤 1 所示,建立 TLS 后, HTTP 请求将进入认证(Authentication)步骤。 集群创建脚本或者集群管理员配置 API 服务器,使之运行一个或多个身份认证组件。 身份认证组件在认证 (opens new window)节中有更详细的描述。 认证步骤的输入整个 HTTP 请求;但是,通常组件只检查头部或/和客户端证书。 认证模块包含客户端证书、密码、普通令牌、引导令牌和 JSON Web 令牌(JWT,用于服务账户)。 可以指定多个认证模块,在这种情况下,服务器依次尝试每个验证模块,直到其中一个成功。 如果请求认证不通过,服务器将以 HTTP 状态码 401 拒绝该请求。 反之,该用户被认证为特定的 username,并且该用户名可用于后续步骤以在其决策中使用。 部分验证器还提供用户的组成员身份,其他则不提供。
# 鉴权
如上图的步骤 2 所示,将请求验证为来自特定的用户后,请求必须被鉴权。 请求必须包含请求者的用户名、请求的行为以及受该操作影响的对象。 如果现有策略声明用户有权完成请求的操作,那么该请求被鉴权通过。 Kubernetes 支持多种鉴权模块,例如 ABAC 模式、RBAC 模式和 Webhook 模式等。 管理员创建集群时,他们配置应在 API 服务器中使用的鉴权模块。 如果配置了多个鉴权模块,则 Kubernetes 会检查每个模块,任意一个模块鉴权该请求,请求即可继续; 如果所有模块拒绝了该请求,请求将会被拒绝(HTTP 状态码 403)。
# 准入控制
准入控制模块是可以修改或拒绝请求的软件模块。 除鉴权模块可用的属性外,准入控制模块还可以访问正在创建或修改的对象的内容。 准入控制器对创建、修改、删除或(通过代理)连接对象的请求进行操作。 准入控制器不会对仅读取对象的请求起作用。 有多个准入控制器被配置时,服务器将依次调用它们。 这一操作如上图的步骤 3 所示。 与身份认证和鉴权模块不同,如果任何准入控制器模块拒绝某请求,则该请求将立即被拒绝。 除了拒绝对象之外,准入控制器还可以为字段设置复杂的默认值。 可用的准入控制模块在准入控制器 (opens new window)中进行了描述。 请求通过所有准入控制器后,将使用检验例程检查对应的 API 对象,然后将其写入对象存储(如步骤 4 所示)。
# 使用 RBAC 鉴权
基于角色(Role)的访问控制(RBAC)是一种基于组织中用户的角色来调节控制对计算机或网络资源的访问的方法。 RBAC 鉴权机制使用 rbac.authorization.k8s.io API 组 (opens new window)来驱动鉴权决定, 允许你通过 Kubernetes API 动态配置策略。 要启用 RBAC,在启动 API 服务器 (opens new window)时将 --authorization-mode 参数设置为一个逗号分隔的列表并确保其中包含 RBAC。
kube-apiserver --authorization-mode=Example,RBAC --<其他选项> --<其他选项>
RBAC API 声明了四种 Kubernetes 对象:
- **Role:**Role 总是用来在某个名字空间 (opens new window)内设置访问权限; 在你创建 Role 时,你必须指定该 Role 所属的名字空间。
- **ClusterRole:**定义集群范围的角色
- **RoleBinding:**角色绑定(Role Binding)是将角色中定义的权限赋予一个或者一组用户。 它包含若干 主体(用户、组或服务账户)的列表和对这些主体所获得的角色的引用。
- **ClusterRoleBinding:**要跨整个集群完成访问权限的授予,你可以使用一个 ClusterRoleBinding。
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""] # "" 默认为 core API 组
resources: ["pods"]
verbs: ["get", "watch", "list"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-sa
namespace: default
---
apiVersion: v1
kind: Secret
metadata:
name: mysecret-sa
namespace: default
annotations:
kubernetes.io/service-account.name: "my-sa"
type: kubernetes.io/service-account-token
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
- kind: ServiceAccount
name: my-sa
# ServiceAccount 的 apiGroup 默认为"",User 和 Group 为"rbac.authorization.k8s.io"
apiGroup: ""
namespace: default
roleRef:
# "roleRef" 指定与某 Role 或 ClusterRole 的绑定关系
kind: Role # 此字段必须是 Role 或 ClusterRole
name: pod-reader # 此字段必须与你要绑定的 Role 或 ClusterRole 的名称匹配
apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-sa-cluster
namespace: default
---
apiVersion: v1
kind: Secret
metadata:
name: mysecret-sa-cluster
namespace: default
annotations:
# 注解绑定service-account,token 会自动创建
kubernetes.io/service-account.name: "my-sa-cluster"
type: kubernetes.io/service-account-token
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
# "namespace" 被忽略,因为 ClusterRoles 不受名字空间限制
name: pod-ns-reader
rules:
- apiGroups: [""]
resources: ["pods", "namespaces"]
verbs: ["get", "watch", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: read-pod-ns-global
subjects:
- kind: ServiceAccount
name: my-sa-cluster # 'name' 是区分大小写的
apiGroup: ""
namespace: default
roleRef:
kind: ClusterRole
name: pod-ns-reader
apiGroup: rbac.authorization.k8s.io
curl测试,或者使用 dashboard 测试
# 默认命名空间下的 pod
curl --location --request GET 'https://192.168.83.133:6443/api/v1/namespaces/default/pods' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Ijg2UUtnd0Nxd3cyY19jOG5sZVNOdE1HYVlPeHJ3eUtkcWhhUzc0ZHpuV0UifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6Im15c2VjcmV0LXNhIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6Im15LXNhIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiZDk1N2YzZDAtNmUzYi00MjM1LWE2NmUtNGM2ODBiMzMxZTBmIiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6bXktc2EifQ.s__X5x1Kq8svZe4PAwe6o5tM1h448u2ygtKF5UevGwBHxRcky0XAHCDniKdbMhh5QBZuVwrswIJLJQTOMZa8-6xcpkdfLA4-tj5Cq_XeNWcsntr8fG2uUOfNipsYpjfznMPCs4o7GGwxkmic07h4ZJ0KNrIQXTtQxoMR7EZwgTkwwf-8J-_J4Zen3iBNXXp4rk5xZoeQLass0tthzxJM1JR9ANKOQEoU4nGodO7rv7dAKS6SbgmpRSNcvfP3lONSVM_6p2dga-u0I2dZV_a5hYs8O-tX7-rfmaUArvIESGWblrf-uB_dO67TrCS3JkiOG1BJM_iTCG38-Oi5gHousQ' -k
# hello 命名空间下的 pod,无权访问
curl --location --request GET 'https://192.168.83.133:6443/api/v1/namespaces/hello/pods' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Ijg2UUtnd0Nxd3cyY19jOG5sZVNOdE1HYVlPeHJ3eUtkcWhhUzc0ZHpuV0UifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6Im15c2VjcmV0LXNhIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6Im15LXNhIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiZDk1N2YzZDAtNmUzYi00MjM1LWE2NmUtNGM2ODBiMzMxZTBmIiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6bXktc2EifQ.s__X5x1Kq8svZe4PAwe6o5tM1h448u2ygtKF5UevGwBHxRcky0XAHCDniKdbMhh5QBZuVwrswIJLJQTOMZa8-6xcpkdfLA4-tj5Cq_XeNWcsntr8fG2uUOfNipsYpjfznMPCs4o7GGwxkmic07h4ZJ0KNrIQXTtQxoMR7EZwgTkwwf-8J-_J4Zen3iBNXXp4rk5xZoeQLass0tthzxJM1JR9ANKOQEoU4nGodO7rv7dAKS6SbgmpRSNcvfP3lONSVM_6p2dga-u0I2dZV_a5hYs8O-tX7-rfmaUArvIESGWblrf-uB_dO67TrCS3JkiOG1BJM_iTCG38-Oi5gHousQ' -k
# 所有命名空间
curl --location --request GET 'https://192.168.83.133:6443/api/v1/namespaces' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Ijg2UUtnd0Nxd3cyY19jOG5sZVNOdE1HYVlPeHJ3eUtkcWhhUzc0ZHpuV0UifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6Im15c2VjcmV0LXNhLWNsdXN0ZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoibXktc2EtY2x1c3RlciIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImExOThiY2IyLThkYTEtNDM3Ni1hMzIxLThkZWQ5NjQ2OGZiZiIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0Om15LXNhLWNsdXN0ZXIifQ.W4_PnHOnGeVvet-IUKXzphub3Opx8_4ReEJFHkIm8tl89nGTVQ_TRkFej78AlyvOhy3qk3BbYlJVi9oueWSSKdF1Ewyi7NkSUsds103CNvUi55JfS5d9VYtI9xV6oDeP38lD74Qb44YhAVz-wMjqwmJ44pOQK80FJWUvKJYMy_NnGjxBSehq2wrjannVYZN8ENWQesE6q3BL1oKpU1HcmSbMoAKiUAEKBR-l10Bl0gxiWtJOdy3XuMpnuPBiVHA7GrBUpxUtm9isOi2VWnRLTtNWvkp1xJD1v1Anfpk4Oibw6H1hkcEVi2FoiVWz2QAYwjcjDevwGi5fPzPGQ70new' -k
# 创建 sa 的临时 token
k create token my-sa-cluster
# 使用 secret 绑定 sa 是永久 token