Deployment

Deployment yaml介绍

apiVersion: apps/v1  #版本号
kind: Deployment  #类型
metadata:    #元数据
  name:    #rs名称
  namespace:   #所属命名空间
  labels:   #标签
    controller: deploy
spec:   #详情描述
  replicas:  #副本数量
  revisionHistoryLimit: #保留历史版本,默认是10,设置为0的话,不保留历史记录
  paused: #暂停部署,默认是false
  progressDeadlineSeconds: #部署超时时间(s),默认是600
  strategy: #策略
    type: RollingUpdates  #滚动更新策略
    rollingUpdate:  #滚动更新
      maxSurge: #最大额外可以存在的副本数,可以为百分比,也可以为整数
      maxUnavaliable: #最大不可用状态的pod的最大值,可以为百分比,也可以为整数
  selector:  #选择器,通过它指定该控制器管理哪些pod
    matchLabels:   #Labels匹配规则
       app: nginx-pod
    matchExpressions:   #Expression匹配规则
      - {key: app, operator: In, values: [nginx-pod]}
  template:  #模板,当副本数量不足时,会根据下面的模板创建pod副本
    metadata:
        labels:
          app: nginx-pod
    spec:
      containers:
      - name: nginx
        image: nginx:1.17.1
        ports:
        - containerPort: 80

扩缩容

kubectl scale deploy deploy名称 --replicas=pod数量 -n 命名空间
kubectl edit deploy deploy名字 -n 命名空间

deployment升级和回滚

命令行创建deployment

kubectl run nginx --image=10.0.0.11:5000/nginx:1.13 --replicas=3 --record

命令行升级版本

kubectl **set image** deploy nginx nginx=10.0.0.11:5000/nginx:1.15 --record

查看deployment所有历史版本

kubectl rollout history deployment nginx

deployment回滚到上一个版本

kubectl rollout undo deployment nginx

deployment回滚到指定版本

kubectl rollout undo deployment nginx --to-revision=2

获取yaml配置

--dry-run -o yaml`,--dry-run代表这条命令不会实际在K8s执行,
-o yaml是会将试运行结果以yaml的格式打印出来,这样我们就能轻松获得yaml配置了
# kubectl create deployment nginx --image=nginx --dry-run -o yaml

金丝雀发布(暂停和恢复)

比如有一批新的pod资源创建完成后立即暂停更新过程,此时,仅存在一部分新版本的应用,主体部分还是旧的版本。
然后,再筛选一小部分的用户请求路由到新的pod应用,继续观察能否稳定地按期望的方式运行。
确定没问题之后再继续完成余下的pod资源滚动更新,否则立即回滚更新操作。这就是所谓的金丝雀发布。
#更新deployment版本,并配置暂停deployment  
kubectl rollout pause deploy pc-deployment -n dev  #先暂停,等修改多个配置后在恢复更新
kubectl set image deploy pc-deployment nginx=nginx:1.17.2 -n dev 
#继续deploy的更新
kubectl rollout resume deploy pc-deployment -n dev 

StatefulSet

statefulset特性

1、稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现
2、稳定的网络标志,即Pod重新调度后其PodName和HostName不变,基于Headless Service(即没有Cluster IP的Service)来实现
3、有序部署,有序扩展,即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次依次进行(即从0到N-1,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态),基于init containers来实现
4、有序收缩,有序删除(即从N-1到0)
5、有序的滚动更新
有状态服务sts比较常见的mongo复制集 ,redis cluster,rabbitmq cluster等等

statefulset创建顺序

在创建StatefulSet之前需要准备的东西,值得注意的是创建顺序非常关键,创建顺序如下:
1、Volume
2、Persistent Volume
3、Persistent Volume Claim
4、Service
5、StatefulSet
Volume可以有很多种类型,比如nfs、glusterfs等,我们这里使用的ceph RBD来创建。

创建statefulset

[root@k8s-master mainfests]# vim stateful-demo.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp-svc
  labels:
    app: myapp-svc
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: myapp-pod
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: myapp
spec:
  serviceName: myapp-svc
  replicas: 3
  selector:
    matchLabels:
      app: myapp-pod
  template:
    metadata:
      labels:
        app: myapp-pod
    spec:
      containers:
      - name: myapp
        image: ikubernetes/myapp:v1
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: myappdata
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: myappdata
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 2Gi

statefulset 扩容和缩容

kubectl scale sts myapp --replicas=2 #扩容和缩容
kubectl patch sts myapp -p '{"spec":{"replicas":2}}'  #打补丁方式缩容

更新策略updateStrategy:

  updateStrategy:
    rollingUpdate:
      partition: 2
    type: RollingUpdate


OnDelete: 删除一个才更新一个
RollingUpdate:滚动更新
以partition方式进行更新,更新值为2,只有myapp编号大于等于2的才会进行更新。类似于金丝雀部署方式。
kubectl patch sts myapp -p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":2}}}}'

级联删除和非级联删除

级联删除:删除sts时同时删除POD
非级联删除:删除sts时不删除POD kubectl delete sts web --cascade=false #使用--cascade参数
此时POD相当于是孤儿,删除POD就不会在重建

DaemonSet

DaemonSet介绍

DaemonSet:服务守护进程,在集群节点上分别部署Pod副本,如果有新节点加入集群,Daemonset会自动的在该节点上运行我们需要部署的Pod副本,相反如果有节点退出集群,Daemonset也会移除掉部署在旧节点的Pod副本。

DaemonSet常用场景:

网络插件的 Agent 组件,如(Flannel,Calico)需要运行在每一个节点上,用来处理这个节点上的容器网络;
存储插件的 Agent 组件,如(Ceph,Glusterfs)需要运行在每一个节点上,用来在这个节点上挂载F远程存储目录;
监控系统的数据收集组件,如(Prometheus Node Exporter,Cadvisor)需要运行在每一个节点上,负责这个节点上的监控信息搜集。
日志系统的数据收集组件,如(Fluent,Logstash)需要运行在每一个节点上,负责这个节点上的日志信息搜集。

DaemonSet更新和回滚

kubectl set image ds nginx nginx=1.20.0 --record #更新
kubectl rollout undo ds nginx --to-revision=number #回滚到number处

DaemonSet yaml案例

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd-elasticsearch
  namespace: kube-system
  labels:
    k8s-app: fluentd-logging
spec:
  selector:
    matchLabels:
      name: fluentd-elasticsearch
  template:
    metadata:
      labels:
        name: fluentd-elasticsearch
    spec:
      tolerations:
      - key: node-role.kubernetes.io/master
        effect: NoSchedule
      containers:
      - name: fluentd-elasticsearch
        image: k8s.gcr.io/fluentd-elasticsearch:1.20
        resources:
          limits:
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      terminationGracePeriodSeconds: 30
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers

HPA

HPA介绍

自动扩缩ReplicationController、Deployment、ReplicaSet 和 StatefulSet 中的 Pod 数量

观察CPU使用率自动扩展和缩容Pod 

自定义指标扩缩容 

kubectl top pod -n kube-system  #查看POD的CPU和内存使用量

必须定义 requst参数 必须安装metrics-server 


          resources:

            requests:

              cpu: 100m

              memory: 100Mi

            limits:

              cpu: 200m

              memory: 200Mi

HPA扩容命令


kubectl autoscale deployment nginx-php --cpu-percent=20 --min=2 --max=5 #CPU占用20% 最少2个,最大5个

kubectl get hpa

kubectl describe hpa

while true;do wget -q -O- http://192.168.222.155:30010 >/dev/null ;done  #测试让CPU使用量提高看HPA能不能扩容

Label&&Selector

Label&Selector介绍

Label:对K8s中各种资源进行分类,分组,添加一个具有特别属性的一个标签。

Selector:通过一个过滤的语法进行查找到对应标签的资源。

加标签

kubectl label node k8s-node-01  region=subnet7

删除标签

kubectl label node k8s-node-01 region-   #  -删除标签

修改标签 

 kubectl label node k8s-node-01 region=subnet8  --overwrite  #overwrite参数

显示标签 

kubectl get node -l region=subnet7
kubectl get po --show-labels  
kubectl get po -A -l  app=nginx #  -A 显示所有

筛选标签

kubectl get po -A -l "k8s-app in (kubernetes-dashboard,metrics-server)" 

# kubernetes-dashboard,metrics-server二个标签同时筛选
kubectl get po -l version!=v1,app=nginx  #version不是v1 ,app=nginx的标签

Service

什么是Service

service会对提供同一个服务的多个pod进行聚合,并且提供一个统一的入口地址。通过访问service的入口地址就能访问到后面的pod服务。

Service常用类型

Type 的取值以及行为如下:

ClusterIP:通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的 ServiceType。
NodePort:通过每个 Node 上的 IP 和静态端口(NodePort)暴露服务。NodePort 服务会路由到 ClusterIP 服务,
          这个 ClusterIP 服务会自动创建。通过请求 <NodeIP>:<NodePort>,可以从集群的外部访问一个 NodePort 服务。 
          在每个安装了kube-proxy的节点上都会打开一个端口,此端口可以代理至后端POD。
LoadBalancer:使用云提供商的负载均衡器,可以向外部暴露服务。外部的负载均衡器可以路由到 NodePort 服务和 ClusterIP 
              服务。
ExternalName:通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容(例如, foo.bar.example.com)。 没有任何类型代理被创建,这只有 Kubernetes 1.7 或更高版本的 kube-dns 才支持。

Service模板

apiVersion: v1
kind: Service
metadata:
  name: redis
  namespace: default
spec:
  selector:           #标签选择器,必须指定pod资源本身的标签
    app: redis
    role: logstor
  type: ClusterIP     #指定服务类型为ClusterIP
  ports:              #指定端口
  - port: 6379        #暴露给服务的端口
  - targetPort: 6379  #容器的端口

Service创建

kubectl expose deployment nginx --port=80 --target-port=80 #直接命令创建SVC
kubectl expose deployment nginx --port=80 --target-port=80 --dry-run=client -o yaml   #导出svc 的 yaml文件
kubectl  get endpoints nginx # 看下自动关联生成的endpoint
kubectl patch svc nginx -p '{"spec":{"type":"NodePort"}}'  # 修改svc的类型来提供外部访问

ipvsadm -ln  # 来看下lvs的虚拟服务器列表

使用Service代理k8s外部服务

先创建一个service 跟之前的service区别是不指定selector

apiVersion: v1
kind: Service
metadata:
  labels:
    app: web-svc-external
  name: web-svc-external
  namespace: default
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  sessionAffinity: None
  type: ClusterIP
  

在创建endpoints 修改IP,就能关联上面的service,起到代理k8s外部服务

apiVersion: v1
kind: Endpoints
metadata:
  name: web-svc-external
  namespace: default
subsets:
- addresses:
  - ip: 36.152.44.96   #外部地址
  ports:
  - name: http
    port: 80
    protocol: TCP

Ingress

Ingress介绍

Ingress是授权入站连接到达集群服务的规则集合,为您提供七层负载均衡能力,您可以通过 Ingress 配置提供外部可访问的 URL、负载均衡、SSL、基于名称的虚拟主机,阿里云容器服务K8S Ingress Controller在完全兼容社区版本的基础上提供了更多的特性和优化。
Ingress是允许入站连接到达集群服务的一组规则。即介于物理网络和群集svc之间的一组转发规则。
其实就是实现L4 L7的负载均衡:
用于实现域名的方式访问K8s内部应用

helm安装ingress

https://kubernetes.github.io/ingress-nginx/deploy/#using-helm

helm upgrade --install ingress-nginx ingress-nginx \
  --repo https://kubernetes.github.io/ingress-nginx \
  --namespace ingress-nginx --create-namespace

ingress暴露服务的方式

1、Deployment+LoadBalancer模式的Service

如果要把ingress部署在公有云,那用这种方式比较合适。用Deployment部署ingress-controller,创建一个type为 LoadBalancer的 service关联这组pod。大部分公有云,都会为 LoadBalancer的 service自动创建一个负载均衡器,通常还绑定了公网地址。只要把域名解析指向该地址,就实现了集群服务的对外暴露

2、DaemonSet+HostNetwork+nodeselector

用DaemonSet结合nodeselector来部署ingress-controller到特定的node 上,然后使用Hostiletwork直接把该pod与宿主机node的网络打通,直接使用宿主机的80/433端口就能访问服务。这时,ingress-controller所在的node机器就很类似传统架构的边缘节点,比如机房入口的nginx服务器。该方式整个请求链路最简单,性能相对NodePort模式更好。缺点是由于直接利用宿主机节点的网络和端口,一个node只能部署一个ingress-controller pod。比较适合大并发的生产环境使用

ingress资源

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: minimal-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - http:
      paths:
      - path: /testpath
        pathType: Prefix
        backend:
          service:
            name: test
            port:
              number: 80

ingress多域名使用

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: name-virtual-host-ingress
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: service1
            port:
              number: 80
  - host: bar.bar.com
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: service2
            port:
              number: 80

HTTPS的配置

创建ssl证书
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj  "/CN=nginXsvc/O=nginxsvc"
创建secret资源进行存储
kubectl create secret tls tls-secret --key tls.key --cert tls.crt
kubectl get secret
kubectl describe secret tls-secret
apiVersion: v1
kind: Secret
metadata:
  name: testsecret-tls
  namespace: default
data:
  tls.crt: #base64 编码的 cert
  tls.key: #base64 编码的 key
type: kubernetes.io/tls
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: tls-example-ingress
spec:
  tls:
  - hosts:
      - https-example.foo.com
    secretName: testsecret-tls
  rules:
  - host: https-example.foo.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: service1
            port:
              number: 80

Secret&ConfigMap

一般用configmap去管理一些配置文件,或者是大量的环境变量信息,configmap将配置和pod分开,有一个nginx,nginx.conf---》configmap.nginx。更易于配置文件的更改和管理

secret:secret根倾向于存储和共享敏感,加密的配置信息。

configmap加密,而secret不加密
https://kubernetes.io/zh-cn/docs/concepts/configuration/secret/ #secret官方中文介绍

secret创建方式

1、使用kubectl create secret方式创建

[root@k8s_master ~]# echo -n 'admin' > ./username.txt
[root@k8s_master ~]# echo -n '1f2d1e2e67df' > ./password.txt
[root@k8s_master ~]# kubectl create secret generic secret01 --from-file=./username.txt --from-file=./password.txt

2、编写yaml文件,引用64位编码

先通过64位编码方式,明文转密文
[root@k8s_master ~]# echo -n 'admin' | base64
YWRtaW4=
[root@k8s_master ~]# echo -n '1f2d1e2e67df' | base64
MWYyZDFlMmU2N2Rm
base64解密: echo "MWYyZDFlMmU2N2Rm" |base64 --decode
编写yaml文件,引用密文创建secret

[root@k8s_master ~]# vim secret02.yaml
apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  username: YWRtaW4=
  password: MWYyZDFlMmU2N2Rm

##创建secret资源
[root@k8s_master ~]# kubectl apply -f secret02.yaml

secret资源的使用方式

第一种:使用secret中的变量导入到pod中

##编写yaml文件
[root@k8s_master ~]# vim secret03.yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: nginx
    image: nginx
    env:
      - name: aaa
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: username
      - name: bbb
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: password
##进入pod,验证secret信息
[root@k8s_master ~]# kubectl exec -it mypod bash
root@mypod:/# echo $aaa
admin
root@mypod:/# echo $bbb
1f2d1e2e67df

第二种:将secret容器以volume的形式挂载到pod的某个目录下

[root@k8s_master ~]# vim secret04.yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod02
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    secret:
      secretName: mysecret
##进入pod,查看secret信息
[root@k8s_master ~]# kubectl exec -it mypod02 bash
root@mypod02:/# cd /etc/foo
root@mypod02:/etc/foo# ls
password  username
root@mypod02:/etc/foo# cat username
adminroot
root@mypod02:/etc/foo# cat password
1f2d1e2e67

imagePullSecrets (容器镜像拉取secret)

你可以使用 imagePullSecrets 将包含 Docker(或其他)镜像仓库密码的 Secret 传递给 kubelet。kubelet 使用此信息来替 Pod 拉取私有镜像

kubectl create secret docker-registry myregistrykey --docker-server=DUMMY_SERVER \
          --docker-username=DUMMY_USERNAME --docker-password=DUMMY_DOCKER_PASSWORD \
          --docker-email=DUMMY_DOCKER_EMAIL
将镜像拉取 Secret 添加到服务账户 
kubectl patch serviceaccount default -p '{"imagePullSecrets": [{"name": "myregistrykey"}]}'
或修改yaml文件
.....
imagePullSecrets:
- name: myregistrykey
.....

ConfigMap概述

https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/configure-pod-configmap/
k8s官方文档configmap介绍

与Secret类似,区别在于ConfigMap保存的是不需要加密配置的信息
configmap 配置中心(明文存放)

1.可以轻松应对各种环境(开发、测试、生产),当启动容器时,根据需求去加载对应的配置文件。
2.当需要对容器内的配置进行修改时,只需要修改配置中心的配置文件。
3.也可以进行灰度发布,针对单个pod内容器进行更新
加载方式 重要
1.当启动pod时,可以将配置中心的配置文件关联到pod上,从中读取数据传递给pod内容器的一个变量,变量注入方式传递给容器;但是当configmap 发生改变时,并不会同步到容器内变量中,仅在pod启动时生效。
2.也可以把configmap当做存储卷,直接挂载到容器内的目录上,应用程序去读取这个目录的配置文件,同时支持自动同步。

configMap创建方式

方式一:使用kubectl创建

[root@k8s_master ~]# vim redis.properties
redis.host=127.0.0.1
redis.port=6379
redis.password=123456
创建configmap资源
[root@k8s_master ~]# kubectl create configmap redis-config --from-file=redis.properties

方式二:变量参数形式
创建configmap资源,定义变量

##编写yaml文件
[root@k8s_master ~]# vim configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: myconfig
  namespace: default
data:
  special.level: info
  special.type: hello

将 ConfigMap 中的所有键值对配置为容器环境变量

创建一个包含多个键值对的 ConfigMap

apiVersion: v1  
kind: ConfigMap  
metadata:  
  name: special-config  
  namespace: default  
data:  
  SPECIAL_LEVEL: very  
  SPECIAL_TYPE: charm

使用 envFrom 将所有 ConfigMap 的数据定义为容器环境变量,ConfigMap 中的键成为 Pod 中的环境变量名称。

apiVersion: v1
  
kind: Pod
  
metadata:
  
  name: dapi-test-pod
  
spec:
  
  containers:
  
    - name: test-container
  
      image: k8s.gcr.io/busybox
  
      command: [ "/bin/sh", "-c", "env" ]
  
      envFrom:
  
      - configMapRef:
  
          name: special-config
  
  restartPolicy: Never

将 ConfigMap 数据添加到一个卷中

apiVersion: v1
kind: Pod
metadata:
  name: dapi-test-pod
spec:
  containers:
    - name: test-container
      image: k8s.gcr.io/busybox
      command: [ "/bin/sh", "-c", "ls /etc/config/" ]
      volumeMounts:
      - name: config-volume
        mountPath: /etc/config
  volumes:
    - name: config-volume
      configMap:
        # 提供包含要添加到容器中的文件的 ConfigMap 的名称
        name: special-config
  restartPolicy: Never

注意:
如果在 /etc/config/ 目录中有一些文件,这些文件将被删除。

Kubernetes-subPath的使用

什么是subPath

为了支持单一个pod多次使用同一个volume而设计,subpath翻译过来是子路径的意思,如果是数据卷挂载在容器,指的是存储卷目录的子路径,如果是配置项configMap/Secret,则指的是挂载在容器的子路径。

subPath的使用场景

1、 1个pod中可以拉起多个容器,有时候希望将不同容器的路径挂载在存储卷volume的子路径,这个时候需要用到subpath

2、volume支持将configMap/Secret挂载在容器的路径,但是会覆盖掉容器路径下原有的文件,如何支持选定configMap/Secret的每个key-value挂载在容器中,且不会覆盖掉原目录下的文件,这个时候也可以用到subpath

subPath的使用

存储卷
采用hostpath的方式创建PV,宿主机的映射目录为/data/pod/volume5
cat pv-subpath.yaml 
kind: PersistentVolume
apiVersion: v1
metadata:
  name: pv-subpath-05
  labels:
    release: stable
spec:
  capacity:
    storage: 0.1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  hostPath:
    path: /data/pod/volume5                 # 宿主机的目录

PV创建成功后,再创建PVC

[root@k8s-master zhanglei]# cat pvc-subpath.yaml 
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-subpath
  namespace: default
spec:
 accessModes: ["ReadWriteOnce"]
 resources:
   requests: 
     storage: 0.05Gi
[root@k8s-master zhanglei]# kubectl create -f pvc-subpath.yaml 

在pod中声明并使用subpath

[root@k8s-master zhanglei]# cat pod-subpath.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: pod-subpath-zltest
spec:
    containers:
    - name: ubuntu-subpath-container
      image: ubuntu
      volumeMounts:
      - mountPath: /var/lib/ubuntu            # 容器1的挂载目录
        name: subpath-vol
        subPath: ubuntutest                   # 宿主机volume5的子目录1
    - name: nginx-subpath-container
      image: nginx
      volumeMounts:
      - mountPath: /var/www/nginx             # 容器2的挂载目录
        name: subpath-vol
        subPath: nginxtest                   # 宿主机volume5的子目录2 
    volumes:
    - name: subpath-vol
      persistentVolumeClaim:
        claimName: pvc-subpath               # PVC的名字
  [root@k8s-master zhanglei]# kubectl create -f pod-subpath.yaml

现在来验证下在宿主机存储卷的目录下是否有2个子目录,1个是ubuntutest用来挂载容器1的,另外1个是nginxtest用来挂载容器2的

进入到容器中,挂载一个文件,验证是否可以同步到存储卷

[root@k8s-master nginxtest]# kubectl exec -it pod-subpath-zltest -c nginx-subpath-container -- bash
root@pod-subpath-zltest:/# cd /var/www/nginx
root@pod-subpath-zltest:/var/www/nginx# ls
nginx-test-subpath.txt
[root@k8s-master volume5]# cd nginxtest/
[root@k8s-master nginxtest]# ls
nginx-test-subpath.txt

可以看到容器1的目录/var/www/nginx 和存储卷的子目录 nginxtest完成了映射,容器2类似,这里不再赘述。

配置项-configMap

1)创建configMap

[root@k8s-master consecret]# cat conf-subpath.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: conf-subpath-zltest
  namespace: default
data:
  example.property.1: hello      # key-value键值对
  example.property.2: world
  example.property.file: |-
    property.1=value-1
    property.2=value-2
    property.3=value-3

2)在Pod中使用configMap

[root@k8s-master consecret]# cat pod-conf-subpath.yaml 
apiVersion: v1
kind: Pod
metadata:
  labels:
    purpose: test-configmap-volume
  name: pod-conf-testvolume
spec:
  containers:
    - name: test-configmap-volume
      image: nginx
      volumeMounts:
        - name: config-volume
          mountPath: /etc/nginx/example.property.1       # 容器挂载目录
          subPath: example.property.1                    # 将key名称作为文件名,hello作为文件内容
  volumes:
    - name: config-volume
      configMap:
         name: conf-subpath-zltest      # 指定使用哪个CM
        
[root@k8s-master consecret]# kubectl create -f pod-conf-subpath.yaml

在容器挂载路径验证下是否将configMap中example.property.1挂载在容器中,且是否会覆盖掉原有的目录

root@pod-conf-testvolume:/# cd  /etc/nginx 
root@pod-conf-testvolume:/etc/nginx# ls
conf.d            fastcgi_params  koi-win    modules     scgi_params   win-utf
example.property.1  koi-utf        mime.types    nginx.conf  uwsgi_params

从上可以看到example.property.1已经挂载到容器中,且未对目录原有的文件进行覆盖

root@pod-conf-testvolume:/etc/nginx# cd example.property.1 
bash: cd: example.property.1: Not a directory
root@pod-conf-testvolume:/etc/nginx# cat example.property.1 
helloroot@pod-conf-testvolume:/etc/nginx# 

从上可以验证configMap的subpath用法支持将configMap中的每对key-value以key名称作为文件名,value作为文件内容挂载到容器的目录中。

configmap和secret热更新

configmap和secret如果使用的是subpath形式挂载,那么POD是感知不到configmap和secret的更新的。
如果pod的变量来自于configmap和secret的定义内容,那么configmap和secret更新后,也不会更新POD中的变量。
edit命令 kubectl edit
直接修改yaml文件 kubectl replace
加--dry-run参数 kubectl create cm --from-file=nginx.conf --dry-run -oyaml |kubectl replace -f-

不可变Secret和ConfigMap
immutable:true 加入这个参数,就不能修改secret和configmap的配置

Volumes

volumes常用类型

K8s volume是一个目录,这点和Docker volume差不多,当Volume被mount到Pod上,这个Pod中的所有容器都可以访问这个volume,在生产场景中,我们常用的类型有这几种:

emptyDir
hostPath
PersistentVolume(PV) & PersistentVolumeClaim(PVC)
StorageClass

emptyDir

在生产中它的最实际实用是提供Pod内多容器的volume数据共享,但是当pod被重启后,emptyDir数据会丢失

# 我们继续用上面的web服务的配置,在里面新增volume配置
# cat web.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: web
  name: web
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - image: nginx
        name: nginx
        resources:
          limits:
            cpu: "50m"
            memory: 20Mi
          requests:
            cpu: "50m"
            memory: 20Mi
        volumeMounts:         # 准备将pod的目录进行卷挂载
          - name: html-files  # 自定个名称,容器内可以类似这样挂载多个卷
            mountPath: "/usr/share/nginx/html"

      - name: busybox       # 在pod内再跑一个容器,每秒把当时时间写到nginx默认页面上
        image: busybox
        args:
        - /bin/sh
        - -c
        - >
           while :; do
             if [ -f /html/index.html ];then
               echo "[$(date +%F\ %T)] hello" > /html/index.html
               sleep 1
             else
               touch /html/index.html
             fi
           done
        volumeMounts:
          - name: html-files  # 注意这里的名称和上面nginx容器保持一样,这样才能相互进行访问
            mountPath: "/html"  # 将数据挂载到当前这个容器的这个目录下
      volumes:
        - name: html-files   # 最后定义这个卷的名称也保持和上面一样
          emptyDir:          # 这就是使用emptyDir卷类型了
            medium: Memory   # 这里将文件写入内存中保存,这样速度会很快,配置为medium: "" 就是代表默认的使用本地磁盘空间来进行存储
            sizeLimit: 10Mi  # 因为内存比较珍贵,注意限制使用大小

hostPath

hostPath Volume 的作用是将容器运行的node上已经存在文件系统目录给mount到pod的容器。在生产中大部分应用是是不会直接使用hostPath的,因为我们并不关心Pod在哪台node上运行,而hostPath又恰好增加了pod与node的耦合,限制了pod的使用

PV&PVC

PV&PVC概念

persistentVolume(简称pv)是由管理员设置的存储,它同样时集群中的一类资源,pv时容量插件,如volumes(卷),但其生命周期独立使用pv的任何pod,pv的创建可使用NFS,CEPH等
persistentVolumeClaim(简称pvc)是用户对存储的请求,类似于pod,pod消耗节点资源,pod可以请求特定级别的资源(cpu和内存),pvc可以请求特定的大小和访问模式,例如,可以以一次读写/写或只读多次的模式挂载。

开始部署NFS-SERVER

# 我们这里在10.0.1.201上安装(在生产中,大家要提供作好NFS-SERVER环境的规划)
# yum -y install nfs-utils

# 创建NFS挂载目录
# mkdir /nfs_dir
# chown nobody.nobody /nfs_dir

# 修改NFS-SERVER配置
# echo '/nfs_dir *(rw,sync,no_root_squash)' > /etc/exports

# 重启服务
# systemctl restart rpcbind.service
# systemctl restart nfs-utils.service
# systemctl restart nfs-server.service

# 增加NFS-SERVER开机自启动
# systemctl enable nfs-server.service
Created symlink from /etc/systemd/system/multi-user.target.wants/nfs-server.service to /usr/lib/systemd/system/nfs-server.service.

# 验证NFS-SERVER是否能正常访问
# showmount -e 10.0.1.201            

创建PV

# cat pv1.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv1
  labels:
    type: test-claim    # 这里建议打上一个独有的标签,方便在多个pv的时候方便提供pvc选择挂载
spec:
  capacity:
    storage: 1Gi     # <----------  1
  accessModes:
    - ReadWriteOnce     # <----------  2
  persistentVolumeReclaimPolicy: Recycle     # <----------  3
  storageClassName: nfs     # <----------  4
  nfs:
    path: /nfs_dir/pv1     # <----------  5
    server: 10.0.1.201
1、capacity 指定 PV 的容量为 1G。
2、accessModes 指定访问模式为 ReadWriteOnce,支持的访问模式有: ReadWriteOnce -- PV 能以 read-write 模式 mount 到单个节点。 ReadOnlyMany -- PV 能以 read-only 模式 mount 到多个节点。 ReadWriteMany -- PV 能以 read-write 模式 mount 到多个节点。
3、persistentVolumeReclaimPolicy 指定当 PV 的回收策略为 Recycle,支持的策略有: Retain -- 需要管理员手工回收。 Recycle -- 清除 PV 中的数据,效果相当于执行 rm -rf /thevolume/*。 Delete -- 删除 Storage Provider 上的对应存储资源,例如 AWS EBS、GCE PD、Azure Disk、OpenStack Cinder Volume 等。
4、storageClassName 指定 PV 的 class 为 nfs。相当于为 PV 设置了一个分类,PVC 可以指定 class 申请相应 class 的 PV。
5、指定 PV 在 NFS 服务器上对应的目录,这里注意,我测试的时候,需要手动先创建好这个目录并授权好,不然后面挂载会提示目录不存在 mkdir /nfsdata/pv1 && chown -R nobody.nogroup /nfsdata 

#创建的pv会有一下几种状态:

Available(可用),没有被PVC绑定的空间资源
Bound (已绑定),已经被pvc绑定
Released (已释放),PVC被删除,但是资源还未被重新使用
Failed (失败),自动回收失败

创建PVC

# cat pvc1.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: pvc1
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: nfs
  selector:
    matchLabels:
      type: test-claim

pod服务来挂载这个pvc

    spec:
      containers:
      - image: nginx
        name: nginx
        volumeMounts:    # 我们这里将nginx容器默认的页面目录挂载
          - name: html-files
            mountPath: "/usr/share/nginx/html"
      volumes:
        - name: html-files
          persistentVolumeClaim:  # 卷类型使用pvc,同时下面名称处填先创建好的pvc1
            claimName: pvc1

pvc&pv绑定问题

很多情况下,创建PVC之后,一直绑定不上PV
1、PVC的空间申请大小大于PV的大小
2、PVC的storageClassName没有和PV的一致
3、PVC的accessModes和PV的不一致

创建挂载了PVC的Pod之后一直处于Pending状态
1、PVC没有被创建成功,或者被创建
2、PVC和Pod不在同一个Namespace

创建PVC后——k8s会创建一个用于回收的POD——根据PV的回收策略进行PV的回收——回收完
以后PV的状态就会变成可被绑定的状态也就是空闲状态——其它的Pending状态的PVC如果匹配到了这个PV,他就能和这个PV进行绑定。

StorageClass动态存储

可以提前配置好pv的动态供给StorageClass,来根据pvc的消费动态生成pv

nfs的StorageClass

apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  namespace: kube-system

---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
rules:
  - 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: ["list", "watch", "create", "update", "patch"]
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]

---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: kube-system
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io

---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: nfs-provisioner-01
  namespace: kube-system
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nfs-provisioner-01
  template:
    metadata:
      labels:
        app: nfs-provisioner-01
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          image: jmgao1983/nfs-client-provisioner:latest
          imagePullPolicy: IfNotPresent
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: nfs-provisioner-01  # 此处供应者名字供storageclass调用
            - name: NFS_SERVER
              value: 10.0.1.201   # 填入NFS的地址
            - name: NFS_PATH
              value: /nfs_dir   # 填入NFS挂载的目录
      volumes:
        - name: nfs-client-root
          nfs:
            server: 10.0.1.201   # 填入NFS的地址
            path: /nfs_dir   # 填入NFS挂载的目录

---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-boge
provisioner: nfs-provisioner-01
# Supported policies: Delete、 Retain , default is Delete
reclaimPolicy: Retain

基于StorageClass创建一个pvc

# vim pvc-sc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: pvc-sc
spec:
  storageClassName: nfs-boge
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Mi

# kubectl  apply -f pvc-sc.yaml
persistentvolumeclaim/pvc-sc created

# kubectl  get pvc
NAME     STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc-sc   Bound    pvc-63eee4c7-90fd-4c7e-abf9-d803c3204623   1Mi        RWX            nfs-boge       3s
pvc1     Bound    pv1                                        1Gi        RWO            nfs            24m

# kubectl  get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM            STORAGECLASS   REASON   AGE
pv1                                        1Gi        RWO            Recycle          Bound    default/pvc1     nfs                     49m
pvc-63eee4c7-90fd-4c7e-abf9-d803c3204623   1Mi        RWX            Retain           Bound    default/pvc-sc   nfs-boge                7s

这里注意下,因为是动态生成的pv,所以它的目录基于是一串随机字符串生成的,这时我们直接进到pod内来创建访问页面

CronJob

一次性任务,在K8s中它叫job
cronjob被调用的时间,是用的controller-manager的时间

Job实例

apiVersion: batch/v1  # 1. batch/v1 是当前 Job 的 apiVersion
kind: Job             # 2. 指明当前资源的类型为 Job
metadata:
  name: myjob
spec:
  completions: 6   # 此job完成pod的总数量
  parallelism: 2   # 每次并发跑2个job
  template:
    metadata:
      name: myjob
    spec:
      containers:
      - name: hello
        image: busybox
        command: ["echo"," hello boge! "]
      restartPolicy: OnFailure     # 3. restartPolicy 指定什么情况下需要重启容器。对于 Job,只能设置为 Never 或者 OnFailure

cronjob是管理job,也就是每一个周期创建一个job去执行任务,一次只能管理一个job

cronjob实例

apiVersion: batch/v1beta1     # <---------  当前 CronJob 的 apiVersion
kind: CronJob                 # <---------  当前资源的类型
metadata:
  name: hello
spec:
  schedule: "* * * * *"      # <---------  schedule 指定什么时候运行 Job,其格式与 Linux crontab 一致,这里 * * * * * 的含义是每一分钟启动一次
  jobTemplate:               # <---------  定义 Job 的模板,格式与前面 Job 一致
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox
            command: ["echo","boge like cronjob."]
          restartPolicy: OnFailure
#这里会显示cronjob的综合信息
#kubectl get cronjobs.batch

# 测试完成后删掉这个资源
# kubectl delete cronjobs.batch hello
cronjob定时任务在生产中的用处很多,这也是为什么上面job我说用得很少的缘故,我们可以把一些需要定时定期运行的任务,在K8s上以cronjob运行,依托K8s强大的资源调度以及服务自愈能力,我们可以放心的把定时任务交给它执行。

注意的参数:concurrencyPolicy:Allow   #并发调度策略,Allow运行同时运行任务
           Forbid:                   #不运行并发执行
           Replace:                  #替换之前的任务
           failedJobsHistoryLimit:1  #保留失败的任务数
           successfulJobsHistoryLimit:3 #成功的Job保留的次数
           suspend:false                #挂起,true cronjob不会被执行
           startingDeadlineSeconds:30   #执行计划任务,失败后,30秒后继续调用,直到成功

三种探针

三种探针类型

启动探针 Startup Probe :用于判断容器内应用程序是否启动,如果配置了startupProbe,就会先禁止其他的探针,直到它成功为止,成功后将不再进行探测。(1.16版本后加入,针对容器内应用服务是否已经启动监控)
就绪探针 Readiness Probe :判断容器是否已经就绪,若未就绪,容器将会处于未就绪,未就绪的容器,不会进行流量的调度。Kubernetes会把Pod从 service endpoints 中剔除。
存活探针 Liveness Probe :判断容器内的应用程序是否正常,若不正常,根据 Pod 的 restartPolicy 重启策略操作,如果没有配置该探针,默认就是success。

探针的三种检查方法

exec:通过在容器内执行指定命令,来判断命令退出时返回的状态码,返回状态码是0表示正常。
httpGet:通过对容器的 IP 地址、端口和 URL 路径来发送 GET 请求;如果响应的状态码在 200 ~ 399 间,表示正常。
tcpSocket:通过对容器的 IP 地址和指定端口,进行 TCP 检查,如果端口打开,发起 TCP Socket 建立成功,表示正常。
配置项
initialDelaySeconds:等待我们定义的时间 结束后便开始探针检查;
periodSeconds:探针的 间隔时间;
timeoutSeconds:探针的 超时时间,当超过我们定义的时间后,便会被视为失败;
successThreshold:探针的 最小连续成功数量;
failureThreshold:探针的 最小连续失败数量;

启动探针

exec方式

    startupProbe:    # 可选 检测容器内进程是否完成启动 注意三种检查方式同时只能使用一种
      failureThreshold: 3 # 失败三次算探针失败
      exec:
        command: ['/bin/sh','-c','echo Hello World']

http方式

      httpGet:
        path: /test
        prot: 80

就绪探针

http方式

    readinessProbe:
      httpGet:
        path: /
        port: 80
      initialDelaySeconds: 3  # 容器启动完成后首次探测的时间,单位为秒
      timeoutSeconds: 2   # 对容器健康检查探测等待响应的超时时间,单位秒,默认1秒
      periodSeconds: 1    # 对容器监控检查的定期探测时间设置,单位秒,默认10秒一次
      successThreshold: 1 # 成功1次算探针OK
      failureThreshold: 3 # 失败三次算探针失败
      restartPolicy: Always # 可选 默认Always

存活探针

http方式

    livenessProbe:
      httpGet:
        path: /
        port: 80
        scheme: HTTP
      initialDelaySeconds: 3  # 容器启动完成后首次探测的时间,单位为秒
      timeoutSeconds: 2   # 对容器健康检查探测等待响应的超时时间,单位秒,默认1秒
      periodSeconds: 1    # 对容器监控检查的定期探测时间设置,单位秒,默认10秒一次
      successThreshold: 1 # 成功1次算探针OK
      failureThreshold: 3 # 失败三次算探针失败
  restartPolicy: Always # 可选 默认Always

exec方式

    livenessProbe:
      initialDelaySeconds: 10   #延迟检测时间
      periodSeconds: 5          #检测时间间隔
      exec:                     #使用命令检查
        command:                #指令,类似于运行命令sh
        - cat                   #sh 后的第一个内容,直到需要输入空格,变成下一行
        - /tmp/healthy          #由于不能输入空格,需要另外声明,结果为sh cat"空格"/tmp/healthy

tcp方式

    livenessProbe:
      initialDelaySeconds: 15
      periodSeconds: 20
      tcpSocket:
        port: 80

容器生命周期钩子

postStart: 容器创建后立即执行,注意由于是异步执行,它无法保证一定在 ENTRYPOINT 之前运行。如果失败,容器会被杀死,并根据 RestartPolicy 决定是 否重启
preStop:容器终止前执行,常用于资源清理。执行完成之后容器将成功终止,如果失败,容器同样也会被杀死。在其完成之前 会阻塞删除容器的操作

    lifecycle:
      postStart:
        exec:    # 在容器启动的时候执行一个命令,修改nginx默认首页内容
          command: ["/bin/sh","-c","echo postStart... > /usr/share/nginx/html/index.html"]
      preStop:
        exec:    # 在容器停止之前停止nginx服务
          command: ["/usr/sbin/nginx","-s","quit"]

Taint&Toleration(污点&容忍)

什么是Taint 和 Toleration

Taint 和 toleration 相互配合,可以用来避免 pod 被分配到不合适的节点上。
每个节点上都可以应用一个或多个taint ,这表示对于那些不能容忍这些 taint 的 pod,是不会被该节点接的。
如果将 toleration 应用于 pod上,则表示这些 pod 可以(但不要求)被调度到具有匹配 taint 的节点上
可以给一个节点添加多个 taint ,也可以给一个 pod 添加多个 toleration。

定义污点和容忍度

污点定义在节点的node Spec中,而容忍度则定义在Pod的podSpec中,它们都是键值型数据,但又都额外支持一个效果effect标记,语法格式为key=value:effect,其中key和value的用法及格式与资源注俯-信息相似, 而effect则用于定义对Pod对象的排斥等级,它主要包含以下三种类型
NoSchedule
不能容忍此污点的新Pod对象不可调度至当前节点,属于强制型约束关系,节点上现存的Pod对象不受影响。
PreferNoSchedule
NoSchedule的柔性约束版本,即不能容忍此污点的新Pod对象尽量不要调度至当前节点,不过无其他节点可供调度时也允许接受相应的Pod对象。节点上现存的Pod对象不受影响。
NoExecute
不能容忍此污点的新Pod对象不可调度至当前节点,属于强制型约束关系,而且节点上现存的Pod对象因节点污点变动或Pod容忍度变动而不再满足匹配规则时,Pod对象将被驱逐。

管理节点的污点

### 设置污点
kubectl taint nodes node1 key1=value1:NoSchedule
### 查看污点
kubectl describe node k8s-node-02 |grep Taints
### 去除污点
kubectl taint nodes node1 key1:NoSchedule-
###删除节点上的全部污点信息
kubectl patch nodes node1 -p '{"spec":{"taints":[]}}'  #通过kubectl patch命令将节点属性spec.taints的值直接置空即可

容忍(Tolerations)

容忍 ( Tolerations ) 的组成
设置了污点的 Node 将根据 taint 的 effect:NoSchedule、PreferNoSchedule、NoExecute 和 Pod 之间产生互斥的关系,Pod 将在一定程度上不会被调度到 Node 上。但我们可以在 Pod 上设置容忍 ( Toleration ) ,意思是设置了容忍的 Pod 将可以容忍污点的存在,可以被调度到存在污点的 Node 上
容忍配置
pod.spec.tolerations

tolerations:
 - key: "key1"
  operator: "Equal"
  value: "value1"  
  effect: "NoSchedule"  
  tolerationSeconds: 3600
 - key: "key1"  
  operator: "Equal"  
  value: "value1"  
  effect: "NoExecute"
 - key: "key2"  
  operator: "Exists"  
  effect: "NoSchedule"

其中 key, vaule, effect 要与 Node 上设置的 taint 保持一致
operator 值为Equal,则表示其key与value之间的关系是equal(等于),也就是key=value. 如果不指定 operator 属性,则默认值为 Equal。operator值为Exists 将会忽略 value 值

1、当不指定 key 值时,表示容忍所有的污点 key(master节点也会部署):

tolerations:
- operator: "Exists"

2、当不指定 effect 值时,表示容忍所有的污点作用

tolerations:
- key: "key"
  operator: "Exists"

3、有多个 Master 存在时,防止资源浪费,可以如下设置(还没有测试)

kubectl taint nodes Node-Name node-role.kubernetes.io/master=:PreferNoSchedule

pod匹配node过程

Kubernetes 处理多个 taint 和 toleration 的过程就像一个过滤器:
一个Pod控制器,查看一个node节点的所有 taint(污点),并开始遍历有那些taint(污点)跟 pod 中存在的toleration (容忍)与之相匹配,这些污点是可以被pod容忍,所以过滤掉。余下未被过滤的 taint(污点) 的 effect 值决定了 pod 是否会被分配到该节点,特别是以下情况:

如果未被过滤的 taint (污点)中存在一个以上 effect 值为 NoSchedule 的 taint,则 Kubernetes 不会将 pod 分配到该节点。
 如果未被过滤的 taint 中不存在 effect 值为 NoSchedule 的 taint,但是存在 effect 值为 PreferNoSchedule 的 taint,则 Kubernetes 会*尝试*将 pod 分配到该节点。
如果未被过滤的 taint 中存在一个以上 effect 值为 NoExecute 的 taint,则 Kubernetes 不会将 pod 分配到该节点(如果 pod 还未在节点上运行),或者将 pod 从该节点驱逐(如果 pod 已经在节点上运行)。

Affinity (亲合力和反亲合力)

nodeSelector

spec:
  containers:
    - name: nginx
      image: nginx:latest
      imagePullPolicy: IfNotPresent
  nodeSelector:
    node: molk8s-node-02

语法kubectl label nodes <node-name> <label-key>=<label-value>
需要注意的是,如果我们指定了Pod的nodeSelector条件,且在集群中不存在包含相应标签的Node,则即使在集群中还有其他可供使用的Node,这个Pod也无法被成功调度。nodeSelector是一种硬性限制。上述设置:nginx1将会被调度到打有标签:node=molk8s-node-02的k8s节点上。但这种做法较为单一且不灵活,很难满足复杂场景的调度设置。

nodeAffinity(Node亲和性调度)

NodeAffinity 意为Node 亲和性的调度策略, 是用于替换NodeSelector的全新调度策略

nodeAffinity也是基于k8s节点标签的一种调度策略,有如下两种限制:
requiredDuringSchedulingIgnoredDuringExecution (硬限制,必须满足, 同nodeSelector,不满足则不进行调度。)
preferredDuringSchedulingIgnoredDuringExecution(软限制,尽量满足,不满足也可正常完成调度)
IgnoreDuringExecution表示如果在Pod运行期间Node的标签发生变化,导致亲和性策略不能满足,则继续运行当前的Pod。

上面的限制也同样适用于:podaffinity和podAntiAffinity。

硬策略

affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
      - matchExpressions:
        - key: nodeType     #key 可以看label有哪些
          operator: In
          values:
          - dev

3.2.软策略

affinity:
  nodeAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
    - weight: 1
      preference:
        matchExpressions:
        - key: nodeType
          operator: In
          values:
          - test
NodeAffinity规则设置的注意事项如下。
◎ 如果同时定义了nodeSelector和nodeAffinity,那么必须两个条件都得到满足,Pod才能最终运行在指定的Node上。
◎ 如果nodeAffinity指定了多个nodeSelectorTerms,那么其中一个能匹配成功即可。
◎ 如果在nodeSelectorTerms中有多个matchExpressions,则一个节点必须满足所有matchExpressions才能运行该Pod。

现在Kubernetes提供的操作符有下面的几种

In:label 的值在某个标签中
NotIn:label 的值不在某个标签中
Gt:label 的值大于某个值
Lt:label 的值小于某个值
Exists:某个 label 存在
DoesNotExist:某个 label 不存在

如果nodeSelectorTerms下面有多个选项的话,满足任何一个条件就可以了;如果matchExpressions有多个选项的话,则必须同时满足这些条件才能正常调度 POD。

podaffinity(Pod亲和与互斥调度策略)
Pod的亲和性和互斥性调度具体作法,就是通过在Pod上定义上增加topologyKey属性,来声明对应的目标拓扑区域内几种相关联的Pod“在一起或不在一起”。与节点亲和性相同,Pod亲和性与互斥的条件设置也是requireDuringSchedulingIgnoredExecution和preferredDuringSchedulingIgnoredExecution。Pod的亲和性被定义与PodSpec的affinity字段的podAffinity子字段中;Pod间的互斥性被定义与同一层次的PodAntiAffinity。

Pod亲和性调度测试
1.参数目标pod
首先创建一个名为pod-flag的Pod,带有标签security=S1和app=nginx,后面例子将使用到pod-flag作为Pod亲和性和互斥的目标Pod:

apiVersion: v1
kind: Pod
metadata:
  name: pod-flag
  labels:
    security: "S1"
    app: "nginx"
spec:
  containers:
  - name: nginx
    image: nginx

Pod 亲和性调度

创建2个Pod来说明Pod亲和性调度,定义亲和性标签"security=S1"对应上面Pod "pod-flag",topologyKey的值被设置为 "kubernetes.io/hostname" :

apiVersion: v1
kind: Pod
metadata:
  name: pod-affinity
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
         matchExpressions:
         - key: security
           operator: In
           values:
            - S1
        topologyKey: kubernetes.io/hostname
  containers:
  - name: with-pod-affinity
    image: nginx

创建Pod之后,使用kubectl get pod -o wide 查看,两个Pod被调度到同一个k8s-node-01上。因为k8s的节点"k8s-node-01"有节点标签key"kubernetes.io/hostname"且这个节点上已经运行具有"security=S1"标签的pod实例,所以pod-affinity会被调度到此节点上。

软亲和力

       affinity:
         podAffinity:
           preferredDuringSchedulingIgnoredDuringExecution:
           - weight: 100
             podAffinityTerm:
               labelSelector:
                 # 因为是Pod亲和性/反亲和性;所以这里匹配规则写的是Pod的标签信息
                 matchExpressions:
                 - key: version
                   operator: In
                   values:
                   - v1
                   - v2
               # 拓扑域  若多个node节点具备相同的标签信息【标签键值相同】,则表示这些node节点就在同一拓扑域
               topologyKey: disk-type

podAntiAffinity(Pod互斥性调度测试)

[root@molk8s-master k8s-affinity]# cat 5-pod-antiaffinity.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx5
  labels:
    app: nginx5
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx5
  template:
    metadata:
      labels:
        app: nginx5
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - nginx5
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: nginx5
        image: nginx:latest

nginx5的两个pod会被调度不同的节点上面去。
假如你还想将nginx5的两个pod调度到特定标签的node上,那可以结合nodeAffinity和podAntiAffinity,见下:

[root@molk8s-master k8s-affinity]# cat 5-pod-antiaffinity.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx5
  labels:
    app: nginx5
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx5
  template:
    metadata:
      labels:
        app: nginx5
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: node
                operator: In
                values:
                -  k8s-node-01
                -  k8s-node-02
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - nginx5
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: nginx5
        image: nginx:latest

nginx5的两个pod将被被调度到具有标签"node=molk8s-node-01"和"node=molk8s-node-02"的两个k8s节点上面,且能保证每个节点只运行一个pod。

topologyKey

# 6.1、既然 topologyKey 是拓扑域,那 Pod 之间怎样才是属于同一个拓扑域?
如果使用 k8s.io/hostname,则表示拓扑域为 Node 范围,那么 k8s.io/hostname 对应的值不一样就是不同的拓扑域。比如 Pod1 在 k8s.io/hostname=node1 的 Node 上,Pod2 在 k8s.io/hostname=node2 的 Node 上,Pod3 在 k8s.io/hostname=node1 的 Node 上,则 Pod2 和 Pod1、Pod3 不在同一个拓扑域,而Pod1 和 Pod3在同一个拓扑域。
如果使用 failure-domain.k8s.io/zone ,则表示拓扑域为一个区域。同样,Node 的标签 failure-domain.k8s.io/zone 对应的值不一样也不是同一个拓扑域,比如 Pod1 在 failure-domain.k8s.io/zone=beijing 的 Node 上,Pod2 在 failure-domain.k8s.io/zone=hangzhou 的 Node 上,则 Pod1 和 Pod2 不属于同一个拓扑域。
当然,topologyKey 也可以使用自定义标签。比如可以给一组 Node 打上标签 custom_topology,那么拓扑域就是针对这个标签了,则该标签相同的 Node 上的 Pod 属于同一个拓扑域。

RBAC权限管理

RBAC:基于角色的访问控制,Role-Based Access Control,他是一种基于企业内个人角色来管理一些资源的访问方法.
Jenkins: 使用角色的用户权限管理
RBAC:4种顶级资源: Role, ClusterRole,RoleBinding,ClusterRoleBinding
Role:角色,包含一组权限的规则.只是附加允许.Namespace隔离,只作用于命名空间内
CluterRole:和Role的区别,Role是只作用于命名空间内.作用于整个集群.
RoleBinding:作用于命令空间内,将ClusterRole或者Role绑定到User,Group,ServiceAccount
ClusterRoleBinding:作用于整个集群.
ServiceAccount:User,Group
Basic-auth-file

https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/rbac/ 官方例子

1、创建K8S 用户

普通用户并不是通过k8s来创建和维护,是通过创建证书和切换上下文环境的方式来创建和切换用户。

a、创建证书

# 创建私钥
$ openssl genrsa -out devuser.key 2048
 
# 用此私钥创建一个csr(证书签名请求)文件
$ openssl req -new -key devuser.key -subj "/CN=devuser" -out devuser.csr
 
# 拿着私钥和请求文件生成证书
$ openssl x509 -req -in devuser.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out devuser.crt -days 365
b、生成账号

$ kubectl config set-credentials devuser --client-certificate=./devuser.crt --client-key=./devuser.key --embed-certs=true
c、设置上下文参数

# # 设置上下文, 默认会保存在 $HOME/.kube/config
$ kubectl config set-context devuser@kubernetes --cluster=kubernetes --user=devuser --namespace=dev

# 查看
$ kubectl config get-contexts

d、设置 默认上下文

$ kubectl config use-context devuser@kubernetes
# 查看
$ kubectl config get-contexts
$ kubectl get nodes

发现使用我们创建的用户查询是失败的,是因为账号还没授权,接下来就是对账号进行授权。这里需要先把用切回来,要不然就无法进行下一步授权了。

$ kubectl config use-context kubernetes-admin@kubernetes
$ kubectl get nodes

2、对用户授权

$ cat >devuser-role-bind<<EOF
kind: Role  # 角色
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: dev
  name: devuser-role
rules:
- apiGroups: [""] # ""代表核心api组
  resources: ["pods"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
kind: RoleBinding # 角色绑定
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: devuser-rolebinding
  namespace: dev
subjects:
- kind: User
  name: devuser   # 目标用户
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: devuser-role  # 角色信息
  apiGroup: rbac.authorization.k8s.io
EOF

执行并验证

$ kubectl apply -f devuser-role-bind
$ kubectl config use-context devuser@kubernetes
$ kubectl get pods # 不带命名空间,这里默认dev,也只能查看dev上面限制的命名空间的pods资源,从而也验证了role是针对命名空间的权限限制
#查看其它命名空间的资源
$ kubectl get pods -n default
$ kubectl get pods -n kube-system
$ kubectl get nodes

可以看到,用devuser,已经可以管理dev命名空间下的pod资源了,也只能管理dev命名空间下的pod资源,无法管理dev以外的资源类型,验证ok。ClusterRoleBinding绑定类似,这里就不重复了。有兴趣的小伙伴可以试试。

切回管理员用户

$ kubectl config use-context kubernetes-admin@kubernetes

2)Group

因为跟user类型,这里就不过多文字介绍,直接上命令和配置

1、创建K8S 用户和用户组

# 创建私钥
$ openssl genrsa -out devgroupuser.key 2048

# 用此私钥创建一个csr(证书签名请求)文件
$ openssl req -new -key devgroupuser.key -subj "/O=devgroup/CN=devgroupuser" -out devgroupuser.csr

# 拿着私钥和请求文件生成证书
$ openssl x509 -req -in devgroupuser.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out devgroupuser.crt -days 365

# 生成账号
$ kubectl config set-credentials devgroupuser --client-certificate=./devgroupuser.crt --client-key=./devgroupuser.key --embed-certs=true

# 设置上下文参数
$ kubectl config set-context devgroupuser@kubernetes --cluster=kubernetes --user=devgroupuser --namespace=dev

# 查看
$ kubectl config get-contexts

2、对组授权

$ cat >devgroup-role-bind.yaml<<EOF
kind: Role  # 角色
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: dev
  name: devgroup-role
rules:
- apiGroups: [""] # ""代表核心api组
  resources: ["services","pods"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
kind: RoleBinding # 角色绑定
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: devgroup-rolebinding
  namespace: dev
subjects:
- kind: Group
  name: devgroup   # 目标用户组
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: devgroup-role  # 角色信息
  apiGroup: rbac.authorization.k8s.io
EOF

执行并验证(命名空间默认dev,而不是系统的default)

$ kubectl apply -f devgroup-role-bind.yaml
#切用户
$ kubectl config use-context devgroupuser@kubernetes
# 查看
$ kubectl config get-contexts
$ kubectl get pods
$ kubectl get svc
$ kubectl get nodes
$ kubectl get jobs

从上图实验看,只能管理dev命名空间下的pods和services了,验证ok。

切回管理员用户

$ kubectl config use-context kubernetes-admin@kubernetes

3)ServiceAccount

上面第四节,已经很详细的介绍了ServiceAccount,这里也是直接上配置和操作命令。

$ cat >RoleBinding-ServiceAccount-001.yaml<<EOF
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: default
  name: role001
rules:
- apiGroups: [""] # "" indicates the core API group
  resources: ["pods"]
  verbs: ["get", "watch", "list"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: rb001
  namespace: default
subjects:
- kind: ServiceAccount
  name: lisi
  namespace: default
roleRef:
  kind: Role
  name: role001
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: lisi
  namespace: default
EOF

执行

$ kubectl delete -f RoleBinding-ServiceAccount-001.yaml
        • *

4)为ServiceAccount生成Token

cat >ClusterRoleBinding-ServiceAccount-token-001.yaml<<EOF
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: admin
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
  name: sa001
  namespace: kube-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: sa001
  namespace: kube-system
EOF

获取token

$ kubectl -n kube-system get secret|grep sa001
$ kubectl -n kube-system describe secret sa001-token-c2klg
# 也可以使用 jsonpath 的方式直接获取 token 的值,如:
$ kubectl -n kube-system get secret sa001-token-c2klg -o jsonpath={.data.token}|base64 -d

【注意】yaml 输出里的那个 token 值是进行 base64 编码后的结果

5)默认的Token

每次创建了新的namespace下都会生成一个默认的token,名为default-token-xxxx。default就相当于该namespace下的一个用户。

$ kubectl -n dev get secret
$ kubectl -n dev describe secret default-token-67wgz
# 也可以使用 jsonpath 的方式直接获取 token 的值,如:
$ kubectl -n dev get secret default-token-67wgz -o jsonpath={.data.token}|base64 -d

可以使用下面的命令给该用户分配该namespace的管理员权限

kubectl create rolebinding $ROLEBINDING_NAME --clusterrole=admin --serviceaccount=$NAMESPACE:default --namespace=$NAMESPACE

  • $ROLEBINDING_NAME必须是该namespace下的唯一的
  • admin表示用户该namespace的管理员权限,关于使用clusterrole进行更细粒度的权限控制请参考RBAC------基于角色的访问控制。
  • 我们给默认的serviceaccount default分配admin权限,这样就不要再创建新的serviceaccount,当然你也可以自己创建新的serviceaccount,然后给它admin权限

其实kubernetes-dashboard就是通过token授权的,可以不清楚的,可以看我之前的文章:Kubernetes(k8s)安装以及搭建k8s-Dashboard详解

总结

RoleBinding 和 ClusterRoleBinding:角色绑定和集群角色绑定,简单来说就是把声明的 Subject 和我们的 Role 进行绑定的过程(给某个用户绑定上操作的权限),二者的区别也是作用范围的区别:RoleBinding 只会影响到当前namespace 下面的资源操作权限,而 ClusterRoleBinding 会影响到所有的namespace。

  • Rule:规则,规则是一组属于不同 API Group 资源上的一组操作的集合
  • Role 和 ClusterRole:角色和集群角色,这两个对象都包含上面的 Rules 元素,二者的区别在于,在 Role 中,定义的规则只适用于单个命名空间,也就是和namespace 关联的,而 ClusterRole 是集群范围内的,因此定义的规则不受命名空间的约束。另外 Role 和 ClusterRole 在Kubernetes中都被定义为集群内部的 API 资源,和我们前面学习过的 Pod、ConfigMap 这些类似,都是我们集群的资源对象,所以同样的可以使用我们前面的kubectl相关的命令来进行操作
  • Subject:主题,对应在集群中尝试操作的对象,集群中定义了3种类型的主题资源:
  1. User:用户,这是有外部独立服务进行管理的,管理员进行私钥的分配,用户可以使用 KeyStone或者 Goolge 帐号,甚至一个用户名和密码的文件列表也可以。对于用户的管理集群内部没有一个关联的资源对象,所以用户不能通过集群内部的 API 来进行管理
  2. Group:组,这是用来关联多个账户的,集群中有一些默认创建的组,比如cluster-admin
  3. ServiceAccount:服务帐号,通过Kubernetes API 来管理的一些用户帐号,和namespace 进行关联的,适用于集群内部运行的应用程序,需要通过 API 来完成权限认证,所以在集群内部进行权限操作,我们都需要使用到 ServiceAccount,这也是我们这节课的重点

临时容器进行Debug 1.16+支持

就是在原有的Pod上,添加一个临时的container,这个container可以包含我们排查问题所有的工具,netstat,ps,top,jstat,imap等

开启临时容器EphemeralContainers

所有节点都需要
#vim /etc/systemd/system/kubelet.service.d/10-kubelet.conf
--feature-gates="EphemeralContainers=true"

#vim /etc/kubernetes/kubelet-conf.yml
featureGates:
  EphemeralContainers: true

#systemctl daemon-reload

vim /usr/lib/systemd/system/kube-proxy.service
--feature-gates=EphemeralContainers=true \

#master节点
vim /usr/lib/systemd/system/kube-apiserver.service
--feature-gates=EphemeralContainers=true \ 
vim /usr/lib/systemd/system/kube-controller-manager.service
--feature-gates=EphemeralContainers=true
vim /usr/lib/systemd/system/kube-scheduler.service
--feature-gates=EphemeralContainers=true
#systemctl daemon-reload
#systemctl restart kubelet kube-apiserver kube-controller-manager kube-scheduler kube-proxy 

debug使用

kubectl debug -it ephemeral-demo --image=busybox:1.28 --target=ephemeral-demo #--target 参数指定另一个容器的进程命名空间
kubectl debug mypod -it --image=busybox
kubectl debug mypod -c debugger --image=busybox #如果用户需要自己指定容器名称则使用
kubectl debug myapp -it --image=ubuntu --share-processes --copy-to=myapp-debug #--share-processes 允许在此 Pod 中的其他容器中查看该容器的进程。

查找问题指南

查看系统Event

kubectl describe po

查看容器日志

kubectl logs <pod_name>
如果在某个Pod中包含多个容器,就需要通过-c参数指定容器的名称来查看
kubectl logs <pod-name> -c <container_name>

查看Kubernetes服务日志

systemctl status <kubernetes_service>
journalctl -u <kubernetes_service>

安装一键式k8s资源平台Ratel到k8s集群中

https://github.com/dotbalo/ratel-doc/blob/master/cluster/Install.md

cat servers.yaml
- serverName: 'test1'
  serverAddress: 'https://192.168.222.150:8443'
  #serverAdminUser: 'xxx'
  #serverAdminPassword: 'xxx#'
  serverAdminToken: 'null'
  serverDashboardUrl: "https://k8s.test1.com.cn/#"
  production: 'false'
  kubeConfigPath: "/mnt/test1.config"
  harborConfig: "HarborUrl, HarborUsername, HarborPassword, HarborEmail"

参数解析:
        serverName: 集群别名
        serverAddress: Kubernetes APIServer地址
        serverAdminUser: Kubernetes管理员账号(需要配置basic auth)
        serverAdminPassword: Kubernetes管理员密码
        serverAdminToken: Kubernetes管理员Token // 暂不支持
        serverDashboardUrl: Kubernetes官方dashboard地址,1.x版本需要添加/#!,2.x需要添加/#
        kubeConfigPath: Kubernetes kube.config路径(绝对路径,这个路径不是宿主机的本地路径,而是1.2小节secret的挂载路径,一般可以不改/mnt)
        harborConfig: 对于多集群管理的情况下,可能会存在不同的harbor仓库,配置此参数可以在拷贝资源的时候自动替换harbor配置
    kubeConfigPath 通过secret挂载到容器的/mnt目录或者其他目录
 mkdir ratel && cd ratel 
 cp /root/.kube/config test1.config

创建Secret:

kubectl create secret generic ratel-config  --from-file=test1.config  --from-file=servers.yaml -n kube-system

部署ratel

[root@k8s-master-01 ratel]# cat ratel.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: ratel
  name: ratel
  namespace: kube-system
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ratel
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: ratel
    spec:
      containers:
        - command:
            - sh
            - -c
            - ./ratel -c /mnt/servers.yaml
          env:
            - name: TZ
              value: Asia/Shanghai
            - name: LANG
              value: C.UTF-8
            - name: ProRunMode
              value: prod
            - name: ADMIN_USERNAME
              value: admin
            - name: ADMIN_PASSWORD
              value: password
          image: registry.cn-beijing.aliyuncs.com/dotbalo/ratel:latest
          imagePullPolicy: Always
          livenessProbe:
            failureThreshold: 2
            initialDelaySeconds: 10
            periodSeconds: 60
            successThreshold: 1
            tcpSocket:
              port: 8888
            timeoutSeconds: 2
          name: ratel
          ports:
            - containerPort: 8888
              name: web
              protocol: TCP
          readinessProbe:
            failureThreshold: 2
            initialDelaySeconds: 10
            periodSeconds: 60
            successThreshold: 1
            tcpSocket:
              port: 8888
            timeoutSeconds: 2
          resources:
            limits:
              cpu: 500m
              memory: 512Mi
            requests:
              cpu: 500m
              memory: 512Mi
          volumeMounts:
            - mountPath: /mnt
              name: ratel-config
      dnsPolicy: ClusterFirst
     # imagePullSecrets:
     #   - name: myregistrykey
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
      volumes:
        - name: ratel-config
          secret:
            defaultMode: 420
            secretName: ratel-config

 Service和Ingress配置

[root@k8s-master-01 ratel]# cat ratel-svc.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app: ratel
  name: ratel
  namespace: kube-system
spec:
  ports:
    - name: container-1-web-1
      port: 8888
      protocol: TCP
      targetPort: 8888
  selector:
    app: ratel
  type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ratel
  namespace: kube-system
spec:
  rules:
  - host: krm.test.com
    http:
      paths:
      - backend:
          service:
            name: ratel
            port:
              number: 8888
        path: /
        pathType: Prefix

K8s资源请求和限额

Request
容器使用的最小资源需求,作为容器调度时资源分配的判断依赖。只有当节点可以分配资源量 >= 容器资源请求数是才允许

Limit
容器可以使用资源的最大值,设置为0表示使用资源无上限。

Request 能够保证POD有足够的资源运行,而Limit 则是防止某个POD无限制地使用资源,导致宿主机雪崩,从而影响其他POD崩溃。两者之间必须满足关系:
0<=Request<=Limit<=Infinity (如果Limit为0表示不对资源进行限制,这时可以小于Request)
20201121162339841.png
QoS(Quality of Service)
当集群资源不足时,K8S会根据Pod标记的QoS类别做剔除决策,腾出空闲资源
20201121162902520.png
第一种标记为保证型,是在内存使用量超限时被kill掉;
第二种标记为突发流量型,当节点资源不足时,这类Pod可能会被kill掉;
第三种标记为最大努力型,只要当节点资源不够调度时,首先就会被kill掉。

resources:
  requests:
    memory: "64Mi"
    cpu: "250m"
  limits:
    memory: "128Mi"
    cpu: "500m"

k8s QoS设计实现

QoS是 Quality of Service 的缩写,即服务质量。为了实现资源被有效调度和分配的同时提高资源利用率,kubernetes针对不同服务质量的预期,通过 QoS(Quality of Service)来对 pod 进行服务质量管理。对于一个 pod 来说,服务质量体现在两个具体的指标:CPU 和内存。当节点上内存资源紧张时,kubernetes 会根据预先设置的不同 QoS 类别进行相应处理。

Guaranteed(有保证的)
Burstable (不稳定的)
Best-Effort (尽最大努力)

对于 QoS 类为 Guaranteed 的 Pod:Pod 中的每个容器必须指定memory requests和memory limit ,CPU requests和 CPU limit ,并且requests 和 limit 要相等。
对于 QoS 类为 BestEffort 的 Pod:Pod 中的容器必须没有设置内存和 CPU 限制或请求
对于 QoS 类为Burstable 的 Pod:Pod 中的一个容器设置了 limit 并且 和 requests 不相等

Best-Effort pods:系统用完了全部内存时,该类型 pods 会最先被kill掉。
Burstable pods:系统用完了全部内存,且没有 Best-Effort 类型的容器可以被 kill 时,该类型的 pods 会被 kill 掉。
Guaranteed pods:系统用完了全部内存,且没有 Burstable 与 Best-Effort 类型的容器可以被 kill 时,该类型的 pods 会被 kill 掉。

PodPreset

PodPreset 的作用

将一些公用的参数设置到pod中去,例如 时区统一设置为东八区等

API Server 开启PodPreset

  • 编辑文件 /etc/kubernetes/manifests/kube-apiserver.yaml,添加配置 --runtime-config=settings.k8s.io/v1alpha1=true,添加PodPreset到--admission-control(新版本是--enable-admission-plugins)

编写Preset YAML

案例PodPreset YAML如下:

apiVersion: settings.k8s.io/v1alpha1
kind: PodPreset
metadata:
  name: demo-podpreset
spec:
  selector:
    matchLabels:
      role: developer
  volumeMounts:
    - mountPath: /cache
      name: cache-volume
  volumes:
    - name: cache-volume
      emptyDir: {}

在这个 PodPreset 的定义中,首先是一个 selector。这就意味着后面这些追加的定义,只会作用于selector 所定义的、带有"role: developer"标签的 Pod 对象,这就可以防止"误伤"。

上面定义了一组 Pod 的 Spec 里的标准字段,以及对应的值。volumeMounts 定义了容器 Volume 的挂载目录,volumes 定义了一个 emptyDir 的 Volume。

配置Pod YAML

案例的Pod YAML文件如下:

apiVersion: v1
kind: Pod
metadata:
  name: test-web
  labels:
    app: test-web
    role: developer
spec:
  containers:
    - name: website
      image: nginx
      ports:
        - containerPort: 80

运行

执行时候需要注意顺序,先执行PodPrest相关YAML创建,再执行Pod相关YAML创建。指令如下

kubectl apply -f test-web-podpreset.yaml
kubectl apply -f test-web.yaml

我们通过使用kubectl get pod指令来检查最终Pod的具体YAML配置。

分析

我们就可以清楚地看到,这个 Pod 里多了新添加的 labels、volumes 和 volumeMount 的定义,它们的配置跟 PodPreset 的内容一样。
此外,这个 Pod 还被自动加上了一个 annotation 表示这个 Pod 对象被 PodPreset 改动过。
PodPreset 里定义的内容,只会在 Pod API 对象被创建之前追加在这个对象本身上,而不会影响任何 Pod 的控制器的定义。
当定义了同时作用于一个 Pod 对象的多个 PodPreset时,Kubernetes 项目会帮合并(Merge)这两个 PodPreset 要做的修改。而如果它们要做的修改有冲突的话,这些冲突字段就不会被修改。

总结

PodPreset 是专门用来对 Pod 进行批量化、自动化修改的工具对象,这也是Kubernetes"一切皆对象"的设计思想的体现。我们还是需要理清楚这些组件的功能,Kubernetes提供了一堆的积木,至于实现什么样的功能还是取决于真实的场景。
不得不说,关于纯粹的CRUD人员要有危机感,Kubernetes真的会消灭掉很多低级和低效岗位。

Rook

什么是Rook

一个自我管理的分布式存储编排系统,它本身并不是存储系统,在存储和K8S之前搭建了一个桥梁,存储系统的搭建或者维护变得特别简单.ROOK支持CSI,CSI做一些PVC的快照,PVC扩容等操作.

Operator:主要用于有状态的服务,或者用于比较复杂的应用管理.
Helm:主要用于无状态的服务,配置分离.
Rook
agent:在每个存储节点上运行,用于配置一个FlexVolume插件,和K8s的存储卷进行集成.挂载网络存储,加载存储卷,格式化文件系统.

Discover:主要用于检测链接到存储节点上的存储设备.

Ceph:
OSD:直接连接每一个集群节点的物理磁盘或者是目录,集群的副本数,高可用性和容错性.
MON:集群监控,所有集群的节点都会向Mon汇报,他记录了集群的拓扑以及数据存储位置的信息.
MDS:元数据服务器.负责跟踪文件层次结构并存储Ceph元数据
RGW:restful API接口.
MGR:提供额外的监控和界面.

Rook安装

官方文档
https://rook.github.io/docs/rook/v1.9/Getting-Started/intro/

$ git clone --single-branch --branch v1.9.9 https://github.com/rook/rook.git
cd rook/deploy/examples
kubectl create -f crds.yaml -f common.yaml -f operator.yaml
kubectl create -f cluster.yaml

共享文件系统类型的StorageClass
https://rook.io/docs/rook/v1.9/ceph-filesystem.html
https://rook.io/docs/rook/v1.9/ceph-filesystem-crd.html

PVC的扩容,PVC的快照和回滚
https://rook.io/docs/rook/v1.9/ceph-csi-drivers.html
开启PVC的扩容和快照功能
https://rook.io/docs/rook/v1.9/ceph-csi-drivers.html

vim /etc/systemd/system/kubelet.service.d/10-kubelet.conf
ExpandCSIVolumes=true,VolumeSnapshotDataSource=true
vim /usr/lib/systemd/system/kube-proxy.service
ExpandCSIVolumes=true,VolumeSnapshotDataSource=true
systemctl daemon-reload
kubectl restart kubelet kube-proxy
vim /usr/lib/systemd/system/kube-apiserver.service
vim /usr/lib/systemd/system/kube-controller-manager.service
vim /usr/lib/systemd/system/kube-scheduler.service

删除集群的文档
https://rook.io/docs/rook/v1.9/ceph-teardown.html

部署一个容器到K8s里

hub.docker.com 搜索官方容器镜像
redis配置文件
https://raw.githubusercontent.com/redis/redis/5.0/redis.conf

operator模板
https://github.com/operator-framework/awesome-operators
redis集群operator布署
https://github.com/ucloud/redis-cluster-operator#deploy-a-sample-redis-cluster

RabbitMQ集群安装
https://github.com/dotbalo/k8s/tree/master/k8s-rabbitmq-cluster

    Install EFK: https://www.cnblogs.com/dukuan/p/9891198.html

    Install RabbitMQ Cluster: https://www.cnblogs.com/dukuan/p/9897443.html

    Install openLDAP: https://www.cnblogs.com/dukuan/p/9983899.html

    Install Redis Sentinel: https://www.cnblogs.com/dukuan/p/9913420.html

    Install Redis Cluster: https://github.com/dotbalo/k8s/tree/master/redis/k8s-redis-cluster

    Install GitLab: https://www.cnblogs.com/dukuan/p/10036489.html

helm安装

https://helm.sh/docs/intro/install

helm常用命令

安装charts
helm install --name redis --namespaces prod bitnami/redis
增加repo
helm repo add stable https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
删除charts
helm uninstall redis
使用helm search repo,您可以在已添加的存储库中找到charts的名称:
#helm search repo redis
更新images
#helm upgrade myapp myapp-2.tgz

helm语法

chart目录结构
Chart.yaml: 该chart的描述文件,包括ico地址,版本信息等
vakues.yaml: 给模板文件使用的变量
charts: 依赖其他包的charts文件
requirements.yaml: 依赖的charts
README.md: 开发人员自己阅读的文件
templates: 存放k8s模板文件目录
NOTES.txt 说明文件,helm install之后展示给用户看的内容
deployment.yaml 创建k8s资源的yaml文件
_helpers.tpl: 下划线开头的文件,可以被其他模板引用.

helm模板语法
模板引用方式,{{ .Release.Name }}, 通过双括号注入,小数点开头表示从最顶层命名空间引用.
helm内置对象
# Release, release相关属性
# Chart, Chart.yaml文件中定义的内容
# Values, values.yaml文件中定义的内容
模板中使用管道
apiVersion: v1
kind: ConfigMap
metadata:
   name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  drink: {{ .Values.favorite.drink | repeat 5 | quote }}
  food: {{ .Values.favorite.food | upper | quote }}
if语句
{{ if PIPELINE }}
# Do something
{{ else if OTHER PIPELINE }}
# Do something else
{{ else }}
# Default case
{{ end }}
操作符, and/eq/or/not
{{/* include the body of this if statement when the variable .Values.fooString exists and is set to "foo" */}}
{{ if and .Values.fooString (eq .Values.fooString "foo") }}
    {{ ... }}
{{ end }}

{{/* do not include the body of this if statement because unset variables evaluate to false and .Values.setVariable was negated with the not function. */}}
{{ if or .Values.anUnsetVariable (not .Values.aSetVariable) }}
   {{ ... }}
{{ end }}
控制语句块在渲染后生成模板会多出空行,需要使用{{- if ...}}的方式消除此空行.如:
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  {{- if eq .Values.favorite.drink "coffee"}}
  mug: true
  {{- end}}
引入相对命名空间,with命令:
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  {{- with .Values.favorite }}
  drink: {{ .drink | default "tea" | quote }}
  food: {{ .food | upper | quote }}
  {{- end }}
range命令实现循环,如:
# values.yaml
favorite:
  drink: coffee
  food: pizza
pizzaToppings:
  - mushrooms
  - cheese
  - peppers
  - onions

#configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  toppings: |-
    {{- range .Values.pizzaToppings }}
    - {{ . }}
    # .表示range的命令空间下的取值
    {{- end }}
    {{- range $key, $val := .Values.favorite }}
    {{ $key }}: {{ $val | quote }}
    {{- end}} 
变量赋值
ApiVersion: v1
Kind: ConfigMap
Metadata:
  name: {{ .Release.Name }}-configmap
Data:
  myvalue: "Hello World"
  # 由于下方的with语句引入相对命令空间,无法通过.Release引入,提前定义relname变量
  {{- $relname := .Release.Name -}}
  {{- with .Values.favorite }}
  food: {{ .food }}
  release: {{ $relname }}
  # 或者可以使用$符号,引入全局命名空间
  release: {{ $.Release.Name }}
  {{- end }}
公共模板,define定义,template引入,在templates目录中默认下划线_开头的文件为公共模板(_helpers.tpl)
# _helpers.tpl文件
{{- define "mychart.labels" }}
  labels:
    generator: helm
    date: {{ now | htmlDate }}
{{- end }}

# configmap.yaml文件
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
  {{- template "mychart.labels" }}
data:
  myvalue: "Hello World"
template语句的升级版本include,template是语句无法在后面接管道符来对引入变量做定义,
include实现了此功能.
# _helpers.tpl文件
{{- define "mychart.app" -}}
app_name: {{ .Chart.Name }}
app_version: "{{ .Chart.Version }}+{{ .Release.Time.Seconds }}"
{{- end -}}

# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
  labels:
    {{- include "mychart.app" . | nindent 4 }}
data:
  myvalue: "Hello World"
  {{- range $key, $val := .Values.favorite }}
  {{ $key }}: {{ $val | quote }}
  {{- end }}
  {{- include "mychart.app" . | nindent 2 }}

# 如果使用template只能手动空格,不能使用管道后的nindent函数来做缩进
一个坑helm install stable/drupal --set image=my-registry/drupal:0.1.0 --set livenessProbe.exec.command=[cat,docroot/CHANGELOG.txt] --set livenessProbe.httpGet=null<br/>,livenessProbe在values.yaml中定义了httpGet,需要手动设置为null,然后设置exec的探针.

helm安装zookeeper和kafka
https://docs.bitnami.com/tutorials/deploy-scalable-kafka-zookeeper-cluster-kubernetes/
pvc没有,把value.yaml里面的persistence: enabled ture改成false

EFK elasticsearch+fluentd+kibana

https://github.com/kubernetes/kubernetes/tree/master/cluster/addons

elasticsearch+filebeat+logstash+kibana
https://github.com/dotbalo/k8s/tree/master/fklek/6.x

Jenkins

https://mirrors.jenkins.io/war-stable/ war包
安装
java -jar jenkins.war --httpPort=8888
后台启动
nohup java -jar jenkins.war --httpPort=8888 &
pipeline中文文档
https://www.jenkins.io/zh/doc/book/pipeline/

1.代码仓库创建项目
2.开发去开发代码逻辑
3.push到gitlab后执行构建
 a).自动构建
 b).手动构建
 c).定时构建
4.Jenkins调用k8s创建pod执行构建
 a) 代码编译
 b) 代码扫描
5.根据Dockerfile生成我们的镜像
 a) 放在对应的项目的根目录下
 b) 放在gitlab统一管理
 c) 每个job配置单独的变量
 d) jar,war-基础镜像
 e) html-html/

  1. push镜像到镜像仓库
  2. Jenkins Slave kubectl ——set 命令 更新我们的镜像
      a) 只更新镜像

  b) helm更新

  1. 判断程序是否启动
      a) -w

    b) 写脚本去判断
  2. 程序启动后,调用测试job

不构建的流水线
 1.jenkins 调用镜像仓库接口,返回镜像tag
 2.选择对于tag进行发版到其他环境

标签: none

添加新评论