MENU

kubernetes进阶:K8S集群配置管理

2019 年 03 月 18 日 • 应用服务器

本篇文章建立在Kubernetes 入门:K8S 基础操作之上。
本篇文章对pod/Service/Ingress/Volume核心应用配置进行深度配置。

深入了解Pod对象

之前大概提过一次,PodK8s中最小部署单元,我们实际的应用都是在pod中运行的,首先podk8S中最小的部署单元,也是一组容器的集合,可以是一个容器或多个容器去组成,可以理解为一个pod相当于一个虚拟机,在虚拟机里,你可以创建多个应用,应用就是容器。在一个pod中的容器共享一个网络命名空间。Pod是短暂的,下面对pod进行了简单的分类

Pod容器分类

在一个pod中,是有多个类型容器的,如下。

Infrastructure Container

基础容器,维护整个POD网络命名空间的,刚刚提到在一个pod中的容器共享一个网络命名空间,就是由基础容器来搞的,当你创建一个应用时,他就会启动起来,对于用户来说是透明的,所以你看到他创建的网络容器,但你能在节点上看到这个容器,譬如上文我们创建了一个nginx的服务,现在去节点看一下。

[root@master-1 ~]# ssh node-1 
Last login: Tue Mar  5 15:47:10 2019 from etcd01
[root@node-1 ~]# docker ps 

你会发现有一个使用pause-amd64镜像启动的容器,如果你有印象会想道这个镜像的地址是在kubelet的配置文件中指定的,也就是这里。

[root@node-1 ~]# cat /usr/local/kubernetes/cfg/kubelet | grep image
--pod-infra-container-image=swr.cn-north-1.myhuaweicloud.com/rj-bai/pause-amd64:3.1"

这个镜像默认是在Google镜像仓库拉取的,但是国内无法访问,再次感谢快活提供国外服务器,在你每次创建pod的时候,都会启动一个pause-amd64容器,一一对应的,这个操作用户是没有感知的,用户不知道他还启了个这个。

Init Containers

这种容器启动优先于业务容器开始执行,之前K8S中,一个pod中有多个容器,容器启动也是没有顺讯的,都是并行启动,这种容器会早于业务容器启动,做一些初始化工作。

Containers

业务容器,也就是具体部署你应用程序的容器,并行启动,没有顺讯,所以在你定义了容器类型后,启动顺讯就是Infrastructure ContainerInit Containers Containers,目前容器就分这三类。

Pod镜像拉取策略

这个是通过imagePullpolicy这个字段来设置的,目前有三种策略,如下。

参数描述
IfNotPresent默认值,镜像在宿主机上不存在才会拉取
Always每次创建一个Pod都会重新拉取一次镜像
NeverPod永远不会主动拉取这个镜像

swarm中也有类似的设置,现在看一下之前创建的那个nginx拉取策略是什么,现在就回用到上面的东西了。

[root@master-1 ~]# kubectl get deployments.apps nginx-deployment -o yaml | grep -i imagepull
        imagePullPolicy: IfNotPresent

现在默认值,没有就回去拉取,之前没设置过,之前拉取的镜像仓库都是公开的,如果要登录就不行了,我目前用得还是阿里云的镜像仓库,以后会考虑用Harbor,要拉去私有仓库的东西,操作如下。

Pod 拉取私有仓库镜像

私有仓库是需要登陆的,认证过了之后就能下载了,所以在YAML文件中要添加一个imagePullSecrets的认证凭据,在本机登陆了并不代表通过k8S去部署时也是以登陆状态去拉取镜像的,dockerK8S使用的凭证并不是一套,是独立的,之前用swarm时需要用的一个参数是--with-registry-auth,这个参数就可以把认证当前的认证信息同步到worker节点,镜像怎么打标签传仓库我就不写了,先来试试不登录直接拉取,现在有一个服务,就是上面我创建的那个nginx,现在把他镜像地址改一下,改成我的私有仓库,然后更新一下服务。

我现在连的是Master节点,刚刚的那些都是在loadbalancer-1上操作的,我现在没有配置文件,就不复制过来了,直接用上面提到的命令把nginxDeployment导成YAML文件改改就行了。

[root@master-1 ~]# mkdir demo
[root@master-1 ~]# cd demo/
[root@master-1 ~/demo]# kubectl get deployments.apps nginx-deployment -o yaml > nginx-deployment.yaml

大概看一下导出的文件吧,内容可能偏多,很多都是默认值,不了解是干啥的参数全部删掉就好,修改后我的文件内容如下。

[root@master-1 ~/demo]# cat nginx-deployment.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: registry.cn-beijing.aliyuncs.com/rj-bai/blog:nginx-1.15.8
        name: nginx
        ports:
        - containerPort: 80

主要就是镜像地址,这是我博客用的nginx镜像,需要登录才能pull的,剩下的就没动过,配置很多,下面在node节点登陆到我的镜像仓库。

[root@master-1 ~/demo]# ansible node -m shell -a "docker login --username=xxx registry.cn-beijing.aliyuncs.com --password xxxx"

都登陆成功了,下面更新下试试。

[root@master-1 ~/demo]# kubectl apply -f nginx-deployment.yaml 
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
deployment.apps/nginx-deployment configured
[root@master-1 ~/demo]# kubectl get pods
nginx-deployment-cd454b696-9blj5    0/1     ImagePullBackOff   0          15s

然后就出错了,提示ImagePullBackOff,是在滚动更新,一个失败之后就不再进行下去了,这个证明了docker&k8s使用的认证不是一套的,查看一下详细的错误信息,命令如下,查看资源的详细信息,现在开始涉及到排错命令了。

[root@master-1 ~/demo]# kubectl describe pods nginx-deployment-cd454b696-9blj5

提示让我登陆,所以失败了,现在就要为他设置一个凭据了,需要用到一个文件,也就是config.json,在~/.docker目录下,这个文件会保存登陆信息,现在需要将它编码,使用base64,命令如下。

[root@master-1 ~/demo]# cat ~/.docker/config.json | base64 -w 0

然后会出来一串,这个就是配置凭据要用到的,我的我就不贴了,然后需要创建一个Secret来管理这个凭据,主要是配置一下加密的数据,YAML如下。

apiVersion: v1
kind: Secret
metadata:
  name: registry-secret
data:
  .dockerconfigjson: 这里是cat ~/.docker/config.json | base64 -w 0的结果。
type: kubernetes.io/dockerconfigjson

然后创建一下。

[root@master-1 ~/demo]# kubectl create -f registry-secret.yaml 
secret/registry-secret created
[root@master-1 ~/demo]# kubectl get secrets 
NAME                  TYPE                                  DATA   AGE
registry-secret       kubernetes.io/dockerconfigjson        1      66s

可以看到data1,如果是0说明数据没有保存进去,所以我们现在拿着个名字就可以配置到文件了。

[root@master-1 ~/demo]# cat nginx-deployment.yaml | grep imagePullS -A 3 -B 1
    spec:
      imagePullSecrets:
      - name: registry-secret
      containers:
      - image: registry.cn-beijing.aliyuncs.com/rj-bai/blog:nginx-1.15.8

可以看到我加到了spec下面,但是现在下载之后这个镜像是无法启动的,他依赖PHP,启动失败不要在意,下面更新一下试试。

[root@master-1 ~/demo]# kubectl apply -f nginx-deployment.yaml 
deployment.apps/nginx-deployment configured
[root@master-1 ~/demo]# kubectl get pods -w
NAME                              READY   STATUS    RESTARTS   AGE
nginx-deployment-7977886b-9v6vd   1/1     Running   0          49s
nginx-deployment-7977886b-c4m6w   0/1     Error     1          51s
nginx-deployment-7977886b-qccg9   0/1     Error     1          81s

-w参数是实时查看创建状态,这里我只是取了一段,根本无法启动,看一下用的镜像是不是我博客的nginx

[root@master-1 ~/demo]# kubectl describe pods nginx-deployment-7977886b-9v6vd | grep image

事实证明是,但是为毛这个镜像起不来,看log

[root@master-1 ~/demo]# kubectl logs -f nginx-deployment-7977886b-9v6vd
nginx: [emerg] host not found in upstream "phpfpm" in /usr/local/nginx/vhost/blog.rj-bai.com.ssl.conf:26

找不到phpfpm这个主机,所以起不来,上面提到过这个依赖php,镜像能正常下载就行了,暂时不管别的。

Pod资源限制

为了防止一个pod占用资源过高影响到宿主机,在K8S中,资源限制主要分为两个方面,其实swarm也一样,一个是limits,一个是requests

limits是对资源的总限制,而requests是在pod创建时分配的最低资源,在创建pod的时候,K8S会根据requests值去做调度分配,也就是说如果你现在有Nnode,现在需要创建一个pod,但是node1的资源已经满足不了创建podrequests值,这个pod就不会被调度到node1,只有满足requests这个值的服务器才会被调度过去,下面一个例子,直接在官网扒的,env自己加的,否则起不来

apiVersion: v1
kind: Pod
metadata:
  name: wp
spec:
  containers:
  - name: db
    image: mysql
    env: 
    - name: MYSQL_ROOT_PASSWORD
      value: "Sowhat?"
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
  - name: wp
    image: wordpress
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

上面Pod有两个容器。每个容器的requests值为0.25 cpu 64MiB内存,因为是两个容器,所以请求最低的资源为0.5 cpu128MiB 内存,也就是pod在创建时需要分配的最低资源,这个值会比limits值低。

每个容器的limits值为0.5 cpu128MiB 内存,所以这个pod的总限制为1 cpu256内存,其实上面写的那些他都是通过docker run的一些命令去实现的,request.cpu等同于用--cpu-shares参数去限制,limits.cpu等同于--cpu-quota参数去限制,内存这块相当于是使用--memory值去限制,大概就是这样,下面去实践一下,就用上面的那个文件。

[root@master-1 ~/demo]# kubectl create -f wp.yaml 
pod/wp created
[root@master-1 ~/demo]# kubectl get pod
NAME                                READY   STATUS    RESTARTS   AGE
wp                                  2/2     Running   0          21s
[root@master-1 ~/demo]# kubectl describe pod wp

可以看到他是被调度到node-2节点了,然后去看一下node-2上所有pod的资源利用率。

[root@master-1 ~]# kubectl describe nodes node-2

重点就是这个,其他的都是之前创建的撒,可以看到CPU Requests500,也就是0.5 CPUCPU Limits1,也就是1 cpuMemory Requests128MMemory Limits256M,后面的百分号是代表目前使用了多少,没问题,和指定的一样,删掉就行了,这块暂时就过了。

Pod重启策略

决定了pod在故障后所做的动作,这个策略是由restartPolicy参数来设置的,,和--restart的选项几乎一样,一共有三个参数

参数描述
Always:容器终止退出后总是重启容器,默认策略
OnFailure:当容器异常退出(退出状态码非0)时才重启容器
Never:当容器终止退出,从不重启容器

现在看一下现在的默认策略,就看之前创建的nginx的吧,之前在创建或更新的时候没有指定过。

[root@master-1 ~]# kubectl get deployments.apps nginx-deployment -o yaml | grep -i restart -C 2
      imagePullSecrets:
      - name: registry-secret
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}

现在默认就是Always,也就是说你容器停了,不管是什么原因停的,他就会帮你再启动一个新的,不是重启,pod是没有重启这个概念的,之前的容器挂了就是挂了,不管,他会直接去启一个新的。

记得前段时间有人叫我把一个项目重启一下,我用的是swarm,那个项目的副本数是三个,我说也没办法进行重启啊,然后想了个损招,登录到worker节点去停项目容器,停了之后swarm会帮你再启动一个新的,哈哈,但是仔细想想还是随便更新一下服务吧,向容器里加了一条host解析,也就是用了--host-add,随便写的,然后服务就开始滚动更新,也算是重启了,在k8S中也一样,服务是无法重启的,这个就不去做演示了,看自己的实际情况去配置吧。

Pod健康检查

健康检查和重启策略有点联系,健康检查(Probe),在默认的情况下,无论是K8S还是swarm是根据容器的状态决定是否要重启的,但是无法判断出容器内部署的应用是否正常,所以就用到了健康检查,用来检测你容器里应用是否存活,保障应用程序在生产环境存活的重要手段,目前为止Probe有两种类型,如下。

类型描述
livenessProbe如果检查失败,杀死容器,根据pod的restartPolicy来操作
readinessProbe如果检查失败,Kubernetes会把Pod从service endpoints中剔除掉

livenessProbe 类型

livenessProbe类型的逻辑是这样,restartPolicy策略上文提到过了,也就是重启策略,如果一个容器健康检查失败,就杀了这个容器,根据重启策略去操作,如果是Always就会重新启动一个新的容器,以恢复他的正常工作,以此类推,这个不用多解释了,下面主要看一下readinessProbe类型的。

readinessProbe 类型

service是为pod提供统一入口,endpoints是一个控制器,用来管理所有pod容器的IP的,下面看一下

[root@master-1 ~]# kubectl get service
[root@master-1 ~]# kubectl get service -n kube-system
[root@master-1 ~]# kubectl get endpoints
[root@master-1 ~]# kubectl get endpoints -n kube-system
[root@master-1 ~]# kubectl get pod -o wide
[root@master-1 ~]# kubectl get pod -o wide -n kube-system

上面提到了ENDPOINTS也是一个控制器,他的作用就是动态感知你所有pod的是由哪些IP组成的,这个IP也就是容器的IP,上图可以看到nginxpod是由三个容器组成的,也有相对应的三个容器IP,并且这个pod被关联到了名为nginx-serviceservice,可以理解为ENDPOINTS控制器相当于一个地址池,但这个地址池是动态的。

service只是提供了一个负载均衡入口,他并不能知道service所关联的pod是哪些容器IP组成的,所以动态感知这些信息是由ENDPOINTS控制器去做的,这样service才知道要把请求转发到哪里。

所以readinessProbe类型的健康检查失败之后,他的逻辑就是将健康检查失败容器的IP在下面的列表中剔除掉。

[root@master-1 ~]# kubectl get endpoints nginx-service 
NAME            ENDPOINTS                                  AGE
nginx-service   12.13.13.2:80,12.13.2.2:80,12.13.69.4:80   23h

因为service是根据这个列表去进行转发的,所以就会不路由到健康检查失败的节点了,目前两种检查策略可以同时使用,也可以单独使用,建议同时使用,livenessProbe算是检查你的容器是否正常,如果检查失败根据重启策略去重启,readinessProbe健康检查算是就绪健康检查,检查你的应用有没有准备好,如果没准备好就不对外提供服务,也就是不会将容器IP加到endpoints里,下面看一下健康检查的方法。

健康检查方法

目前健康检查有三种方法,如下所示

方法描述
httpGet发送http请求,返回200-400状态码视为成功
exec执行shell命令返回状态码为0视为成功
tcpSocket发起TCP Socket 建立成功视为成功

看实际情况选择使用哪种方法吧,说白了httpGet就是获取http响应头的状态码,exec就是在容器内执行一条命令去判断返回码,tcpSocket就是判断能否和目标端口建立连接,下面实践一下。

实践操作

分别使用三种方法,各个都试试,不过我只是配置了一种检查类型,不要在意,有些参数要注意一下。

httpGet

[root@master-1 ~/demo]# cat nginx-pod.yaml 
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:latest
    livenessProbe:
      httpGet:
        path: /
        port: 8080
        scheme: HTTP
      initialDelaySeconds: 3
      periodSeconds: 5
      timeoutSeconds: 1
      successThreshold: 1
      failureThreshold: 3

用到的参数

参数描述
pathhttp服务器上请求访问的路径
port要访问容器上的哪个端口
scheme连接端口的协议(HTTP/HTTPS),默认HTTP,要大写撒
initialDelaySeconds容器启动后多长时间开始健康检查
periodSeconds监控频率,默认10s,最小1s
timeoutSeconds超时秒数,默认1s,最小值为1s
successThreshold检查失败之后最小成功连续次数,默认1,活跃度必须为1
failureThreshold检查失败次数,超了这个次数直接根据重启策略进行操作

目前只有livenessProbe方式,常用的参数就这些,端口我是故意写错的,下面启动一下。

[root@master-1 ~/demo]# kubectl create -f nginx-pod.yaml 
pod/nginx created
[root@master-1 ~/demo]# kubectl get pods -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE
nginx   1/1     Running   0          4s    12.13.13.2   node-3   <none>
[root@master-1 ~/demo]# kubectl get pods -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE
nginx   1/1     Running   1          29s   12.13.13.2   node-3   <none>

可以看到无法启动了,其实端口不对,检查失败,无限重启,看一下日志。

[root@master-1 ~/demo]# kubectl describe pods nginx | grep -i live
    Liveness:       http-get http://:8080/ delay=3s timeout=1s period=5s #success=1 #failure=3
[root@master-1 ~/demo]# kubectl describe pods nginx

可以看到在健康检查时连接被拒绝,接下来容器被杀死后重新创建,说明健康检查没有问题,但总是检查失败已经进入无限重启的状态了,现在跑的是nginx,启动属于是秒级的,像是真的部署JAVA项目的时候,initialDelaySeconds参数肯定得调了,最低30s,其他的就没什么了,下面试试tcpSocket方法的。

tcpSocket

下面用nginx的模板改改一下,使用也是用livenessProbe方式,方法使用tcpSocket,文件内容如下。

[root@master-1 ~/demo]# cat redis-pod.yaml 
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:latest
    livenessProbe:
      tcpSocket:
        port: 80
      initialDelaySeconds: 3
      periodSeconds: 3
      timeoutSeconds: 1
      successThreshold: 1
      failureThreshold: 3

用到的参数还是那些,直接创建,一次成功就对了。

[root@master-1 ~/demo]# kubectl create -f nginx-pod.yaml 
pod/nginx created
[root@master-1 ~/demo]# kubectl get pods nginx -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE
nginx   1/1     Running   0          49s   12.13.69.3   node-2   <none>
[root@master-1 ~/demo]# kubectl describe pods nginx | grep -i live
    Liveness:       tcp-socket :80 delay=3s timeout=1s period=3s #success=1 #failure=3

没啥子问题,现在想办法在不停掉容器的情况下把这个nginx弄崩了,看看会不会自动重启,在node2上,现在可以搞事了,健康检查检查的是80端口,现在把默认的端口换了,重载nginx就行了。

[root@master-1 ~]# kubectl exec -it nginx /bin/bash
root@nginx:/# sed -i 's#80#81#g' /etc/nginx/conf.d/default.conf 
root@nginx:/# nginx -s reload
2019/03/11 04:52:41 [notice] 16#16: signal process started
root@nginx:/# command terminated with exit code 137
[root@master-1 ~]# 

被杀了,看一下现在的状态。

[root@master-1 ~]# kubectl get pods nginx -o wide
NAME    READY   STATUS    RESTARTS   AGE     IP           NODE     NOMINATED NODE
nginx   1/1     Running   1          4m43s   12.13.69.3   node-2   <none>

看一下日志。

[root@master-1 ~]# kubectl describe pods nginx

阔以看到健康检查失败了,容器被杀重启了,这个tcpSocket比较适合去检查非http服务,像是什么zookeeper&redis之类的东西,最后看一下exec

exec

这种方法就是在容器中执行命令,命令返回结果为0表示健康,还是以nginx的那个改一下

[root@master-1 ~/demo]# cat nginx-pod.yaml 
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:latest
    livenessProbe:
      exec: 
        command: 
        - cat 
        - /etc/nginx/nginx.conf
      initialDelaySeconds: 3
      periodSeconds: 5
      timeoutSeconds: 1
      successThreshold: 1
      failureThreshold: 3

我定义的健康检查方式为cat /etc/nginx/nginx.conf这个文件,如果这条命令能执行成功就算健康的,一会正常启动之后吧nginx.conf文件删了就cat不到了,所以先创建一下吧。

[root@master-1 ~/demo]# kubectl create -f nginx-pod.yaml 
pod/nginx created
[root@master-1 ~/demo]# kubectl get pods -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP          NODE     NOMINATED NODE
nginx   1/1     Running   0          39s   12.13.2.2   node-1   <none>

正常启动了,然后去容器里吧hosts文件删了。

[root@master-1 ~/demo]# kubectl exec -it nginx /bin/bash
root@nginx:/# \rm /etc/nginx/nginx.conf 
root@nginx:/# command terminated with exit code 137

看日志。

[root@master-1 ~/demo]# kubectl describe pods nginx    

还是检查失败容器重启了,这个也可以执行脚本,所以这个适合检查比较复杂的应用程序,上面就是个栗子,自行琢磨吧。

健康检查的使用目的就是为了防止容器内的服务异常,如果出现异常会重建容器,保证pod的可用性,在加了健康检查之后,在服务滚动更新创建或是扩展的时候,如果容器健康检查失败,这个容器是不会对外提供服务的,直到健康检查成功之后才会接收请求,生产环境必加。

Pod调度约束

目的是可以将制定的Pod调度的制定的节点上,默认的调度是根据节点的资源利用率去调度,在我们用命令行创建一个Pod的时候,他大概会经历这些步骤。

基于write的机制来做组件之间的协作,当用户创建一个pod时,apiserver接收到创建pod的请求,apiserver会将写请求写入到etcd中,你要创建什么样的podpod具有什么属性,都会被写进去。

然后Scheduler调度器通过watch获取到etcd中创建的新pod,调度器根据调度算法,选出新创建的pod应该被调度到那个节点上,然后更新到etcd中,kubectl也会通过watchetcd中获取到绑定自己的pod,这个的理解就是譬如我当前是node-1上的kubelet,我就会拉取etcd中绑定到node-1pod,获取到之后通过docker run 将容器启动起来,启动起来之后再更新到etcd中,就是报告当前pod的状态,是ContainerCreating状态还是Running状态,我们通过kubectl get pod去查看的时候,会再次向apiserver发送一个请求,他会获取到etcd中当前pod的状态,然后展现给用户的,调度的流程就是这样。

指定调度约束

目前可以通过两个字段来指定,分别如下。

字段释义
nodeName用于将Pod调度到指定的Node名称上
nodeSelector用于将Pod调度到匹配Label的Node上

以下是实例。

nodeName

还是用刚刚的nginx吧,加一行就行了,指定到node-1节点上运行。

[root@master-1 ~/demo]# cat nginx-pod.yaml | grep spec -C 2
    app: nginx
  name: nginx
spec:
  nodeName: node-1
  containers:
[root@master-1 ~/demo]# kubectl create -f nginx-pod.yaml 
pod/nginx created
[root@master-1 ~/demo]# kubectl get pods -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP          NODE     NOMINATED NODE
nginx   1/1     Running   0          16s   12.13.2.2   node-1   <none>

大概就是这样,现在就是在node-1上面了,下面用nodeSelector试一下。

nodeSelector

既然是使用标签,就需要先给Node节点打一下标签,下面给节点打一个标签。

[root@master-1 ~]# kubectl label nodes node-1 regin=server
node/node-1 labeled
[root@master-1 ~]# kubectl label nodes node-2 regin=server
node/node-2 labeled
[root@master-1 ~]# kubectl label nodes node-3 regin=nginx
node/node-3 labeled
[root@master-1 ~]# kubectl get nodes --show-labels

可以看到有一些默认的标签,这是K8S默认为node打的标签,删除标签的方法如下。

[root@master-1 ~]# kubectl label nodes node-1 regin-

好了,现在指定一下nginxpod只能运行在标签为nginxnode-3节点上,改一下配置文件就行了。

[root@master-1 ~/demo]# cat nginx-pod.yaml | grep regin -C 2
spec:
  nodeSelector: 
    regin: nginx
  containers:
  - name: nginx
[root@master-1 ~/demo]# kubectl create -f nginx-pod.yaml 
pod/nginx created
[root@master-1 ~/demo]# kubectl get pods -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE
nginx   1/1     Running   0          17s   12.13.13.2   node-3   <none>

调度过来了撒,大概就是这样,这东西作用还是很大的。

Pod故障排查

部署只是起步,后期的维护少不了故障排查,如果你创建的pod出问题了,到底要怎么排查,下面提供一些思路

pod的状态

也就是这里的状态,STATUS下的

[root@master-1 ~/demo]# kubectl get pods -o wide
NAME    READY   STATUS    RESTARTS   AGE     IP           NODE     NOMINATED NODE
nginx   1/1     Running   0          7m29s   12.13.13.2   node-3   <none>

现在是Running状态,该状态表示Pod已经绑定到某个节点,并且已经创建了所有容器,至少有一个容器在运行中,或正在启动或重新启动,除了Running还有以下的状态。

描述
Pendingpod已经提交给apiserver了,但是由于某种原因不能正确创建,例如下载镜像慢,或是调度失败
Successdedpod中所有容器都已成功终止,不会重新启动
FailedPod中所有容器被终止,最少有一个容器在故障中终止,容器要么非0退出,要么被系统终止
Unknown由于某种原因apiserver无法或得Pod状态,通常是Master与Pod所在主机kubelet通讯时出错

出现如上问题可以使用以下三个命令去排查。

使用kubectl describe查看pod的事件,使用kubectl logs查看容器内日志,使用kubect exec进入到容器中查看应用处于什么样的状态,所以需要了解一个调度的流程,这几个命令之前几乎都用过了。

深入了解Service

说白了就是与外界连通,大概分为5个部分。

  • Service定义。
  • PodService的关系
  • Service类型。
  • Service代理模式。
  • DNS

部署一个应用,例如写一个deployment,里面有一个字段replicas来指定副本数,一个应用会用很多的副本分布在各个节点上,如果某一个pod由于某种原因崩溃掉了,replicas会帮你再启一个pod,保证副本数量,新启的这个pod肯定是和原来一样的,重启之后容器的IP就发生变化了,但是要怎么动态感知容器IP呢?这就引入了Service的概念,Service可以动态感知全部PodIP,并且能提供对外访问入口。

Service的设计目的主要是防止Pod失联,他能获取到所有PodIP,所以你不用管容器的IP是什么了,全部交给Service去做就可以了。可以定义Pod的访问策略,也提供了负载均衡的功能,帮你将请求转发到后端的一个pod中,可以支持三种模式,分别是ClusterIP&NodePort&LoadBalancerService的底层实现主要是由iptablesIPVS两种网络模式,这两种模式决定了怎么去转发流量。

Service 定义

先贴一个Service的例子

[root@master-1 ~/demo]# cat nginx-service.yaml 
apiVersion: v1  ## API 版本
kind: Service   ## 绑定的资源对象为Service
metadata:             ## Service源数据,指定service名称和工作空间
  name: nginx-service   ## Service名称
  namespace: default    ## Service工作空间
spec:             
  clusterIP: 10.0.0.213   ## 默认使用cluster IP 
  ports:                ## port 来定义Service的入口和容器的入口
  - name: http            ##指定端口名称
    port: 80              ## Service 入口
    protocol: TCP         ## 使用协议
    targetPort: 80        ## pod端口,也就是容器端口
  selector:            ##标签选择器,通过标签来匹配要关联的Pod
    app: nginx            ##关联到标签为app:nginx 的pod

用这个配置文件创建一个Service

[root@master-1 ~/demo]# kubectl create -f nginx-service.yaml 
service/nginx-service created
[root@master-1 ~/demo]# kubectl get service
NAME            TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes      ClusterIP   10.0.0.1     <none>        443/TCP   11d
nginx-service   ClusterIP   10.0.0.213   <none>        80/TCP    4m15s

发现有一条默认的Service,他的作用是在集群内部访问apiserver的,通过kubernetes这个DNS名称。

Service要想动态感知后端IP的变化,他会用到一个名为endpoints的控制器,上面扯健康检查的时候提到了这个,也就是每个Service会对应一个endpoints控制器,endpoints来帮他关联后端的podService只是关联某一组Pod,动态感知的具体实现时交给endpoints控制器来完成的,下面看一下。

[root@master-1 ~]# kubectl get endpoints 
NAME            ENDPOINTS                               AGE
kubernetes      192.168.1.200:6443,192.168.1.201:6443   11d
nginx-service   12.13.13.2:80                           13m

第一行为apiserverIP及端口,默认就会创建这一条Service,第二行就是我们刚刚创建的那个。

回到Service,看上文类型为ClusterIP,也是默认的Service类型,分配的IP是我们刚制定的213,如不指定会有一个随机的IP,那个deployment之前删掉了,现在加回来。

[root@master-1 ~/demo]# kubectl create -f nginx-deployment.yaml
[root@master-1 ~/demo]# kubectl get endpoints 
NAME            ENDPOINTS                                  AGE
kubernetes      192.168.1.200:6443,192.168.1.201:6443      11d
nginx-service   12.13.13.3:80,12.13.2.2:80,12.13.69.3:80   25m

endpoints这里决定了将数据包转发到哪里,关联到这个Service是通过标签的,也就是上文的。

  selector:            ##标签选择器,通过标签来匹配要关联的Pod
    app: nginx            ##关联到标签为app:nginx 的pod

下面查一下具有nginx标签的pod,通过下面的命令。

[root@master-1 ~]# kubectl get pod -l app=nginx -o wide
NAME                                READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE
nginx-deployment-6c98dfbf68-2kpdt   1/1     Running   0          14m   12.13.69.3   node-2   <none>
nginx-deployment-6c98dfbf68-6dhxr   1/1     Running   0          14m   12.13.2.2    node-1   <none>
nginx-deployment-6c98dfbf68-z6n74   1/1     Running   0          14m   12.13.13.3   node-3   <none>

目前是有三个pod,可以看到容器IP和运行节点,容器IP和上面的对比一下,也就是说如果现在有个容器崩了,他帮你又拉起一个容器,新容器新的IP地址,由ENDPOINTS控制器帮你感知到新启动的容器IP和端口,并加入到相对应的Service中,这块对用户来说完全是透明的,查看这个Service的详细信息。

[root@master-1 ~]# kubectl describe service nginx-service 

Pod与Service的关系

上文提到过,说白了就是Service通过标签选择器来匹配一组Pod,通常来说这组Pod都是在deployment里面定义的,现在看一下之前创建的nginx-deployment标签那里是怎么写的,取了主要的。

[root@master-1 ~]# kubectl get deployments.apps nginx-deployment -o yaml | grep -i labels -C 2
deployment.kubernetes.io/revision: "1"
  creationTimestamp: 2019-03-11T09:16:02Z
  generation: 1
  labels:
    app: nginx
  name: nginx-deployment
  namespace: default
--
  replicas: 3
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: nginx
  strategy:
    rollingUpdate:
--
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      containers:

可以看到写了标签的位置有三处,作用如下。

  labels:
    app: nginx

第一处这个标签可以定义多个,但是不要重复

  selector:
    matchLabels:
      app: nginx

此为控制器使用的标签匹配,

  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx

这个是Pod标签,如果控制器要匹配到这一组Pod,控制器标签和Pod标签要保证一致,才能匹配到,Service实现了Pod的负载均衡,提供的是四层负载均衡,也就是TCP/UDP级别的。

Service 类型

有三种类型,分别如下。

  • ClusterIP

    • 默认类型,分配一个集群内部可以访问的虚拟IP(VIP),主要用于集群内部通讯
  • NodePort

    • 在每个Node上分配一个端口作为外部访问入口。
  • LoadBalancer

    • 工作在特定Cloud Provider上,如谷歌云,aws,OpenStack

ClusterIP

流量转发用的是iptables/IPVS去实现的,接收到请求后选一个Pod IP & Port将流量转发过去,访问流程大概就是请求先到iptables/IPVS→Service→PodService逻辑上将Pod绑定到一起,对于用户是无感知的,你只需要知道ServiceIP即可,这个主要是集群内部用的,下面的就是之前写的一个Cluster类型的Service

[root@master-1 ~/demo]# cat nginx-service.yaml 
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  namespace: default
spec: 
  clusterIP: 10.0.0.213
  ports: 
  - name: http 
    port: 80
    protocol: TCP
    targetPort: 80
  selector: 
    app: nginx

NodePort

应用部署到K8S集群中,你面临的问题就是如何让用户访问到你部署的应用,这就会用到最常用的一种方式,NodePort就会每一个node上暴露一个端口出去,作为访问的统一入口,基于上面的配置改一下。

[root@master-1 ~/demo]# cat nginx-service.yaml 
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  namespace: default
spec: 
  type: NodePort
  ports: 
  - name: http 
    port: 80
    protocol: TCP
    targetPort: 80
    nodePort: 30001
  selector: 
    app: nginx
[root@master-1 ~/demo]# kubectl create -f nginx-service.yaml 
service/nginx-service created
[root@master-1 ~/demo]# kubectl get service nginx-service 
NAME            TYPE       CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
nginx-service   NodePort   10.0.0.47    <none>        80:30001/TCP   20s

上面的Service就是将标签为nginxPod上的80端口发布到了Node节点的30001端口,Node节点的端口范围是在apiserver中指定的,也就是这里。

[root@master-1 ~]# cat /usr/local/kubernetes/cfg/kube-apiserver  | grep service-node-port
--service-node-port-range=30000-50000 \

访问一下试试。

没问题,这个就是以IPVS方式去做的负载均衡,全部节点能用ipvsadm看到规则,下面看一下,全部node节点安装ipvsadm,执行命令看一下。

[root@master-1 ~]# ansible node -m yum -a "name=ipvsadm state=installed"
[root@master-1 ~]# ansible node -m shell -a "ipvsadm -ln | grep 30001 -A 3"

没问题,代理模式一共是两种,一种是iptables,另一种就是现在用的ipvs,一会会细说。

现在有一个问题,暴露端口的范围只是30000-50000,所以实际应用的时候还会加一个代理,譬如一个nginx,使用代理服务器作为访问入口,upstream地址池随便写几个node-IP:port就行了,Pod少这样玩还可以,Pod多了够你受的,之后的文章会提到Ingress会完美解决这个问题,先提一下,

如果真的用NodePort+代理服务发布服务,NodePort的访问流程如下。

用户请求→代理服务器→NodeIP:PortPodIP:Port

LoadBalancer

运行在特定的云提供商的,不适合用户自建的K8S集群,他的原理和NodePort差不多,但是自建集群要加代理的时候我需要知道后端节点的IP及Service暴露出的端口,也就是在这里的信息。

[root@master-1 ~]# kubectl get service nginx-service 
NAME            TYPE       CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
nginx-service   NodePort   10.0.0.47    <none>        80:30001/TCP   49m

80端口为Pod端口,30001端口是在Node节点暴露的端口,所以外部访问的端口为30001,我需要知道这个端口才能把代理和Service关联起来,上文我指定了这个端口,不指定的话就是随机的,但是使用公有云的LoadBalancer就不用了,他是自动帮你添加创建的Service及生成的端口和每个NodeIpLoadBalancerLoadBalancer提供特定云提供商底层LB接口,直接用去调用就好,他和NodePort的访问流程其实一样,就是他关联后端节点端口是自动的。

Service 代理模式

其实流量转发是由每个Node上部署的kube-proxy组件完成的,网络代理有两种模式,一种是iptables,另一种是IPVS,目前默认使用的都是iptables,现在IPVS属于是稳定版,在部署的时候采用的就是IPVS,看一下kube-proxy的配置文件。

[root@master-1 ~]# ansible node-1 -m shell -a "cat /usr/local/kubernetes/cfg/kube-proxy | grep mode -A 1"
node-1 | CHANGED | rc=0 >>
--proxy-mode=ipvs \
--masquerade-all=true \

下面来了解一下两个模式分别是怎么工作的。

iptables

iptables属于是一个内核级的防火墙,当你创建一个Service的时候,kube-proxy会帮你在所有Node上生成iptables规则,在Node节点可以通过iptables-save查看所有规则,规则的多少在于你Pod&Service的数量,目前用的是ipvs模式,现在将Node-1节点切换至iptables模式,默认用的就是iptables模式,所以把配置文件中的指定删掉就行了,也就是这两行。

[root@node-1 ~]# cat /usr/local/kubernetes/cfg/kube-proxy | grep mode -A 1
--proxy-mode=ipvs \
--masquerade-all=true \
[root@node-1 ~]# systemctl daemon-reload 
[root@node-1 ~]# systemctl restart kube-proxy.service

看一下日志,会看到一个如下的字段,说明启用iptables成功了。

[root@node-1 ~]# cat /var/log/messages|grep  "Using iptables Proxier"
Mar 13 11:50:59 node-1 kube-proxy: I0313 11:50:59.911883   19020 server_others.go:148] Using iptables Proxier.

看一下之前创建的Service

[root@master-1 ~]# kubectl get service nginx-service 
NAME            TYPE       CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
nginx-service   NodePort   10.0.0.203   <none>        80:30001/TCP   16h

现在在一个Node-1节点上检索一下iptables规则,找带有10.0.0.203的规则。

[root@node-1 ~]# iptables-save | grep "10.0.0.203"
-A KUBE-SERVICES ! -s 10.0.0.0/24 -d 10.0.0.203/32 -p tcp -m comment --comment "default/nginx-service:http cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.0.0.203/32 -p tcp -m comment --comment "default/nginx-service:http cluster IP" -m tcp --dport 80 -j KUBE-SVC-6IM33IEVEEV7U3GP

阔以看到有两条规则,主要是第二条,意思是有请求要访问10.0.0.203这个IP,目标端口是80的都会跳转到一个名为KUBE-SVC-6IM33IEVEEV7U3GP的链中,这就是在你创建Service的时候iptables模式会先创建这条规则,然后我们再看一下KUBE-SVC-6IM33IEVEEV7U3GP链都有啥子规则,主要是下面的这三条。

[root@node-1 ~]# iptables-save | grep "KUBE-SVC-6IM33IEVEEV7U3GP"
-A KUBE-SVC-6IM33IEVEEV7U3GP -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-BWUSBT3UVFFHQ6AE
-A KUBE-SVC-6IM33IEVEEV7U3GP -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-J4GSBIQTXZHJBMDH
-A KUBE-SVC-6IM33IEVEEV7U3GP -j KUBE-SEP-C2KTRD32Q7MZN5B7

三个Pod会对应有三条链,这是一组规则的集合,这组规则就实现了怎么做负载均衡,你可以理解为规则有一个权重值,例如第一条--probability 0.33332999982,这表示有30%的几率会被匹配到这条规则上,iptables的规则匹配是从上到下的,第二条有50%的几率,如果上两条都没匹配到,那就走最后一条了,然后你又会发现这三条规则分别又关联了三条不一样的链,检索一下这三条链都写了什么。

[root@node-1 ~]# iptables-save | egrep "KUBE-SEP-BWUSBT3UVFFHQ6AE|KUBE-SEP-J4GSBIQTXZHJBMDH|KUBE-SEP-C2KTRD32Q7MZN5B7"

可以看到一个DNAT的操作,这个操作会将数据包转发到后面的IP:Port上,上面可以看到一共是三个,这三个正是Service代理的IP和端口,看一下ENDPOINTS控制器。

[root@master-1 ~]# kubectl get endpoints nginx-service 
NAME            ENDPOINTS                                  AGE
nginx-service   12.13.13.2:80,12.13.2.2:80,12.13.69.3:80   20h

大概就是这样,不难理解,每个endpoints都有相对应的iptables规则,这是由kube-proxy组件搞定的,出现变化也会刷新这些规则,这就是使用iptables做流量转发和负载均衡实现的方法,负载均衡就是用了iptables一个权重值的概念来实现轮训转发,流量转发使用了DNAT帮你转发到具体的Pod中,下面来看看IPVS是怎么工作的。

IPVS

再来了解一下IPVS模式是怎么工作的,在使用iptables的情况下,会发现有两个问题,第一个创建一个Serviceiptables就会创建很多规则,也会涉及到更新,例如我一个pod挂掉了重新启了一个,IP变了,iptables的规则就会刷新,这是一个非增量式的更新,规则越多,消耗越大。

其二这些规则都是从上到下逐条匹配的,如果你Service&Pod越来越多,那自然的你iptables规则就越来越多,问题就是匹配会有延时,因为一个请求进来,iptables会从上到下进行匹配,规则越多匹配越慢,这两个问题很致命,在大规模的情况下不建议使用iptables模式,而是使用IPVS模式。

相信很多人都用过LVS,其实LVS就是通过IPVS内核调度模块实现的负载均衡,LVS只支持四层负载均衡,如果你用过LVSIPVS你就很好理解了,一个内核级的调度模块。

使用了IPVS模式之后,它也是由kube-proxy来维护的,当你创建一个Service,他会生成一个虚拟设备kube-ipvs0,看一下。

[root@master-1 ~]# ansible node -m shell -a "ip addr| grep ipvs"

这个设备的IP绑定的正是所有ServiceIP,也就是CLUSTER-IP,都会被绑定到这里,可以看到之前nginx-service使用的203在这里,再看一下IPVS规则,也找nginx-service的。

[root@master-1 ~]# ansible node -m shell -a "ipvsadm -ln | grep "10.0.0.203" -A 3"

这种规则啥子意思应该都能看懂吧,kube-proxy通过IPVS去刷新规则,IPVS会比iptables的性能好很多,因为IPVS是工作在内核态,而iptables是工作在用户态,IPVS维护成本也比较低,不像iptables那么麻烦。

iptables与IPVS小结

对比一下,iptables比较灵活,功能也很强大,但问题就是在于更新规则都是全量去更新的,规则越多,操作越慢,再就是规则匹配这块,从上到下匹配,规则越多,匹配越慢。

IPVS是工作在内核态的,有更好的性能,而且调度算法也支持很多,iptables暂时只支持轮训,IPVS可以设置很多调度算法,默认rr(轮训),还可以使用wrr(加权轮询)&lc(最小连接)&wlc(加权最小连接)&ip hash,在实际生产环境中最好使用ipvs,具体制定使用什么算法可以在配置文件中指定,使用如下参数

--ipvs-scheduler=算法

一般不会去动他,默认的就够用了,下面开始部署集群内部DNS服务。

部署集群内部DNS服务

集群内部是通过ClusterIP进行通讯,但是这个地址也不是固定的,在我们这里项目连接基础服务都是通过DNS名称去连接的,之前swarm的时候就说过,Swarm是自带DNS模块的,在早期的版本用的是kube-dns,但是设计的很复杂,不容器维护,现在默认DNScoredns,现在要部署一下了,YAML文件位置。

[root@master-1 /usr/local/kubernetes/soft/kubernetes/cluster/addons/dns/coredns]# pwd
/usr/local/kubernetes/soft/kubernetes/cluster/addons/dns/coredns
[root@master-1 /usr/local/kubernetes/soft/kubernetes/cluster/addons/dns/coredns]# mv coredns.yaml.sed coredns.yaml
[root@master-1 /usr/local/kubernetes/soft/kubernetes/cluster/addons/dns/coredns]# cat coredns.yaml | grep -E "image:|clusterIP|local"
        kubernetes cluster.local in-addr.arpa ip6.arpa {
        image: coredns/coredns:1.2.6
  clusterIP: 10.0.0.2

要改的位置是这三个,那个clusterIP是之前在kubelet.config配置文件中指定的,我之前指定的是0.2,所以这里也是0.2,镜像地址也得改,默认的无法下载,就是这样,可以创建了撒。

[root@master-1 /usr/local/kubernetes/soft/kubernetes/cluster/addons/dns/coredns]# kubectl create -f coredns.yaml 
serviceaccount/coredns created
clusterrole.rbac.authorization.k8s.io/system:coredns created
clusterrolebinding.rbac.authorization.k8s.io/system:coredns created
configmap/coredns created
deployment.extensions/coredns created
service/kube-dns created
[root@master-1 ~]# kubectl get pods -n kube-system 
NAME                                    READY   STATUS    RESTARTS   AGE
coredns-57b8565df8-257lq                1/1     Running   0          42s

正常启动了撒,下面测试一下这个DNS是否工作正常。

[root@master-1 ~]# kubectl run -it --image=busybox:1.28.4 --rm --restart=Never sh
If you don't see a command prompt, try pressing enter.
/ # 

登陆到容器了,现在用nslookup试试能不能查到之前创建的Service

可以解析到,说明DNS工作正常,他请求的地址就是0.2,也就是coredns的地址,coredns会实时获取你创建Service的动态,为每一个Service创建DNS记录用于域名解析,现在是在default命名空间下的,我要解析kube-system空间下的就要这样了。

/ # nslookup kubernetes-dashboard
Server:    10.0.0.2
Address 1: 10.0.0.2 kube-dns.kube-system.svc.cluster.local

nslookup: can't resolve 'kubernetes-dashboard'
/ # nslookup kubernetes-dashboard.kube-system
Server:    10.0.0.2
Address 1: 10.0.0.2 kube-dns.kube-system.svc.cluster.local

Name:      kubernetes-dashboard.kube-system
Address 1: 10.0.0.33 kubernetes-dashboard.kube-system.svc.cluster.local
/ # 

就是需要加一个.<命名空间>就可以了,如果不加命名空间他是会解析当前命名空间下,这就是DNSService固定一个DNS名称,你访问这个名称就相当于访问CLUSTER-IP,他的名称是根据Service名字决定的,根据自己的情况来定吧。

部署Ingress Controller

大概分为三个部分

  • Pod与Ingress的关系
  • Ingress Controller
  • Ingress 实现HTTP于HTTPS

在上面接触到了NodePortLoadBalancer可以把应用暴露给外界进行访问,能感觉到需要提前规划端口,应用越来越多就会变得很麻烦,并且每个Service都会给你创建一个负载均衡,维护成本有点高,Ingress就是一个全局的负载均衡器,能提供统一的访问入口,他可以通过域名或URL将请求转发到不同的Service,他支持七层的负载均衡,而之前提到的那些都是四层的,Ingress是授权请求入站访问到集群的规则集合,具体的实现是由Ingress Controller来完成的,先了解一下PodIngress的关系。

Pod与Ingress的关系

Ingress是通过Service来关联Pod的,通过你指定的Service来绑定一组pod,通过Ingress Controller来实现Pod的负载均衡,Ingress只是定义了规则,负载均衡是由控制器(Controller)来完成的,他支持四层和七层的转发,我们下面用到的就是ingress-nginx

Ingress Controller

具体帮你提供全局负载均衡,了解一下访问流程,用户会先访问Ingress控制器定义的规则,这个规则你可以理解为nginx配置文件写的各个虚拟主机,用户请求会先到达Ingress控制器,控制器是在node上运行的,然后再到具体的pod,我们用的是Ingress-nginx,这个官方维护的,其实还有很多,具体还有哪些可以看一下官方文档,下面部署一下Ingress-nginx

部署Ingress-nginx

[root@master-1 ~/demo]# wget -O Ingress-nginx.yaml https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/mandatory.yaml

需要改两处,一处是镜像地址,默认的下载特别慢,慢到你无法想象,所以我提前下载好了,第二个就是要使用主机网络,所以我改了这些。

[root@master-1 ~/demo]# cat Ingress-nginx.yaml | egrep -i  "image|hostNetwork" -C 1
    spec:
      hostNetwork: true
      serviceAccountName: nginx-ingress-serviceaccount
--
        - name: nginx-ingress-controller
          image: swr.cn-north-1.myhuaweicloud.com/rj-bai/nginx-ingress-controller:0.23.0
          args:

生产环境下建议指定一下服务约束,测试无所谓了,可以部署了

[root@master-1 ~/demo]# kubectl create -f Ingress-nginx.yaml
[root@master-1 ~/demo]# kubectl get pod -n ingress-nginx -o wide
NAME                                        READY   STATUS    RESTARTS   AGE   IP              NODE     NOMINATED NODE
nginx-ingress-controller-6858dbdc8b-bbv5v   1/1     Running   0          27m   192.168.1.203   node-2   <none>

好了,运行状态,这个控制器就能帮你实现全局的负载均衡,下面开始定义Ingress的规则了。

Ingress定义HTTP

想通过Ingress暴露你的服务你必须是使用域名的,这个就比较蛋疼了,像是我们这里暴露接口出去都是没有域名的,而且暴露端口出去的时候比较多,后期真的用K8S考虑加域名吧,下面是一个实例,直接在官网扒过来的,顺便改了改。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: rj-bai
spec:
  rules:
  - host: rj-bai.com
    http:
      paths:
      - path: /
        backend:
          serviceName: nginx-service
          servicePort: 80

host那一行改成你的域名,serviceName也就是你要通过这个Ingress把哪个Service暴露出去,我写的就是nginx-service,我之前创建的,servicePort就是Service内部端口,也就是80,看一下这个Service

[root@master-1 ~]# kubectl get service nginx-service 
NAME            TYPE       CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
nginx-service   NodePort   10.0.0.119   <none>        80:30001/TCP   21h

直接创建吧

[root@master-1 ~/demo]# kubectl create -f rj-bai.com.yml 
ingress.extensions/rj-bai created
[root@master-1 ~/demo]# kubectl get ingresses.extensions -o wide
NAME     HOSTS        ADDRESS   PORTS   AGE
rj-bai   rj-bai.com             80      12s

然后测一下,写hosts吧。

[root@master-1 ~]# curl -I rj-bai.com
HTTP/1.1 301 Moved Permanently
Server: rj-bai
[root@master-1 ~]# cat >>/etc/hosts<<OEF
> 192.168.1.203 rj-bai.com
> OEF
[root@master-1 ~]# curl -I rj-bai.com
HTTP/1.1 200 OK
Server: nginx/1.15.9

就是这样子,没问题,但具体是怎么实现的,我们进到控制的容器看看就知道了。

[root@master-1 ~]# kubectl exec -it -n ingress-nginx nginx-ingress-controller-6858dbdc8b-bbv5v /bin/bash
www-data@node-2:/etc/nginx$ nginx -V
nginx version: nginx/1.15.9
built by gcc 8.2.0 (Debian 8.2.0-21) 

说白了这就是一个nginx的容器,你操作Ingress控制器他就会创建相对应的upsteam配置,不过这个upstream地址池是动态的,配置不是写在文件里的,而是写在了内存里,原理就是这样,如果还有别的域名继续写配置文件创建规则就行了,下面看看https的。

Ingress 定义HTTPS

既然是https了,就需要有证书了撒,我刚刚创建的域名是rj-bai.com,所以我直接用我的证书了,而且是可信任的撒,需要创建一个secret来保存tls证书信息,下面开始吧。

[root@master-1 ~/demo/ssl]# ls
rj-bai.com.crt  rj-bai.com.key
[root@master-1 ~/demo/ssl]# kubectl create secret tls rj-bai.com --cert=rj-bai.com.crt --key=rj-bai.com.key 
secret/rj-bai.com created
[root@master-1 ~/demo/ssl]# kubectl get secrets rj-bai.com 
NAME         TYPE                DATA   AGE
rj-bai.com   kubernetes.io/tls   2      29s

这就创建完了,在secrets里也阔以看到撒,有两个数字信息,下面编写一下Ingress规则。

[root@master-1 ~/demo]# cat rj-bai.com-ssl.yaml 
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: rj-bai.com-ssl
spec:
  tls: 
  - hosts: 
    - rj-bai.com
    secretName: rj-bai.com
  rules:
  - host: rj-bai.com
    http:
      paths:
      - path: /
        backend:
          serviceName: nginx-service
          servicePort: 80

注意一下tls那里的配置,别的就和上面一样了,在创建之前先把之前的删了,

[root@master-1 ~/demo]# kubectl delete -f rj-bai.com.yaml 
ingress.extensions "rj-bai" deleted
[root@master-1 ~/demo]# kubectl create -f rj-bai.com-ssl.yaml 
ingress.extensions/rj-bai.com-ssl created
[root@master-1 ~/demo]# curl -I rj-bai.com
HTTP/1.1 308 Permanent Redirect
Server: nginx/1.15.9
[root@master-1 ~/demo]# curl -I https://rj-bai.com
HTTP/1.1 200 OK
Server: nginx/1.15.9

可以看到以非https方式访问他帮你重定向到https了,重定向状态码为308,说真的这个状态码我也是第一次见,事实证明没问题,如果你的证书是自签的,使用curl -k参数可以忽略证书问题,如果证书指定错了,k8s就是用默认的证书了。

总结一下Ingress支持四层七层负载均衡转发,支持自定义Service访问策略,支持TLS,但是只支持基于域名的网站访问,所以还是建议用这种方式去暴露你的服务,下面看看数据卷。

Volume数据卷

分两类,一类是本地数据卷,一类是网络数据卷,容器都是暂时的,他不会持久的在某个节点上,他的数据是默认保存在当前容器里的,容器没了啥都没了,所以就要使用Volume了,pod需要设置来源(spec.volume)和挂载点(spec.containers.volumeMounts)两个信息后就可以使用相应的Volume,先看看本地数据卷。

本地数据卷

主要用的两种,一个是emptyDir,另一个是hostPath,分别了解一下。

emptyDir

这个数据卷会在你的宿主机创建一个空目录,然后挂载到Pod容器,Pod删除这个卷也会被删除,这个应用场景就是Pod之间数据共享,说白了就是你想在容器中做数据共享就要用这个卷了,下面一个例子。

[root@master-1 ~/demo]# cat emptyDir.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: emptydir-pod
spec: 
  containers: 
  - image: centos:latest
    name: write
    command: ["bash","-c","for i in {1..200};do echo $i >> /data/rj-bai;sleep 1;done"]
    volumeMounts: 
    - mountPath: /data
      name: data

  - name: read
    image: centos:latest
    command: ["bash","-c","tail -f /data/rj-bai"]
    volumeMounts: 
    - mountPath: /data
      name: data
  volumes:
  - name: data
    emptyDir: {}

这个pod一共是启动了两个容器,第一个容器执行了一条命令,应该都能看懂,向/data/rj-bai文件写数据,一秒一次,第二个容器读取这个文件,注意这是读取的本地文件,不使用emptyDir的情况下各个容器之间的文件系统是隔离的。

后三行为数据卷来源,emptyDir用的格式就是{},定义一个空目录,这个空目录挂载容器的位置是由volumesMounts下的mountPath来定义的,我挂载到了/data,具体挂载哪个数据卷是通过名字来分辨的,数据卷名我用的是data,这里可以定义多个数据卷类型,通过名字去定义挂载哪一个,下面启动查看吧。

[root@master-1 ~/demo]# kubectl create -f emptyDir.yaml 
pod/emptydir-pod created
[root@master-1 ~/demo]# kubectl get pods emptydir-pod 
NAME           READY   STATUS    RESTARTS   AGE
emptydir-pod   2/2     Running   0          76s
[root@master-1 ~/demo]# kubectl logs emptydir-pod --tail=10 -c read
[root@master-1 ~/demo]# kubectl logs emptydir-pod --tail=10 -c write

没啥子问题,一个在实时写,一个在实时读,大概就是这样,后期收集日志的时候会用到这个,过。

hostPath

这个就是挂载node节点宿主机目录到容器中,这个用的比较多,栗子,按着之前的改改就行了。

[root@master-1 ~/demo]# cat hostpath.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: hostpath-pod
spec: 
  containers: 
  - image: centos:latest
    name: write
    command: ["bash","-c","for i in {1..200};do echo $i >> /data/rj-bai;sleep 1;done"]
    volumeMounts: 
    - mountPath: /data
      name: data
  volumes:
  - name: data
    hostPath: 
      path: /tmp
      type: Directory

意思时间宿主机的/tmp目录挂载到容器的/data目录,类型为目录,操作还是向/data/rj-bai写数据,启动之后看运行节点的/tmp/rj-bai文件就行了,开始创建。

[root@master-1 ~/demo]# kubectl create -f hostpath.yaml 
pod/hostpath-pod created
[root@master-1 ~/demo]# kubectl get pod hostpath-pod -o wide
NAME           READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE
hostpath-pod   1/1     Running   0          40s   12.13.69.3   node-2   <none>

运行在了node2节点,所以现在看一下node2节点的文件就知道了。

[root@master-1 ~]# ansible node-2 -m shell -a "cat /tmp/rj-bai | tail"

没问题撒,就是这种结果,容器没了文件还在,但要注意宿主机要挂在的目录一定要存在,否则Pod根本无法启动,下面玩玩网络存储,使用NFS

NFS网络卷

emmmm,既然是用NFS,现在还没有,所以需要安装一下,master-1作为服务端,下面把包装一下。

[root@master-1 ~]# yum -y install nfs-utils rpcbind

这个是服务端,所有的node节点要安装如上软件包以支持挂载NFS,但不用启动。

[root@master-1 ~]# ansible node -m yum -a "name=nfs-utils state=installed"

下面要陪配置一下服务端的nfs配置完之后启动守护进程即可。

[root@master-1 ~]# mkdir /nfs-data
[root@master-1 ~]# cat /etc/exports
/nfs-data *(rw,no_root_squash)
[root@master-1 ~]# systemctl start rpcbind.service
[root@master-1 ~]# systemctl start nfs-server.service
[root@master-1 ~]# showmount -e localhost
Export list for localhost:
/nfs-data *

nfs的配置不用多说了,现在可以通过编译YAML文件将NFS目录挂载到容器了,不需要你手动挂载,交给K8S去做就行了撒,照着之前的nginx改改就行了,也方便做测试。

[root@master-1 ~/demo]# cat nginx-nfs.yaml 
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      imagePullSecrets:
      - name: registry-secret
      containers:
      - image: nginx:1.15.8
        name: nginx
        ports:
        - containerPort: 80
        volumeMounts: 
        - name: wwwroot
          mountPath: /usr/share/nginx/html
      volumes: 
      - name: wwwroot
        nfs: 
          server: 192.168.1.200
          path: /nfs-data
---

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  namespace: default
spec: 
  type: NodePort
  ports: 
  - name: http 
    port: 80
    protocol: TCP
    targetPort: 80
    nodePort: 30001
  selector: 
    app: nginx

NFS数据卷使用方法是和本地数据卷一样的,设置了数据源和容器位置,我是挂到了nginx默认根目录,设置一下NFS服务地址和路径,当然得先把之前的删掉,然后在创建。

[root@master-1 ~/demo]# kubectl delete -f nginx-nfs.yaml 
deployment.apps "nginx-deployment" deleted
service "nginx-service" deleted
[root@master-1 ~/demo]# kubectl create -f nginx-nfs.yaml 
deployment.apps/nginx-deployment created
service/nginx-service created
[root@master-1 ~/demo]# kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-55d57dff75-crslc   1/1     Running   0          25m
nginx-deployment-55d57dff75-dx9f9   1/1     Running   0          25m
nginx-deployment-55d57dff75-jj8nf   1/1     Running   0          25m

正常启动了撒,下面测试一下,这个Service绑定过域名,访问一下试试。

[root@master-1 ~]# curl https://rj-bai.com
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.15.8</center>
</body>
</html>

现在是没权限,暂定为找不到index.html,现在向nfs目录创建一个,写点数据进去,没意外的话就能访问到了。

[root@master-1 ~]# echo rj-bai > /nfs-data/index.html
[root@master-1 ~]# curl https://rj-bai.com
rj-bai

没问题,说明NFS挂载正常,这就是pod实现了远程存储,数据卷这块就是暂时就是这样,过。

深入管理Pod

K8S中去管理pod的任务是由控制器(controllers)去做的,控制器又称为工作负载(workload),有了控制器才能更好的对服务进行编排,有五个比较重要的控制器,如下。

  • Deployment
  • StatefulSet
  • DaemonSet
  • Job
  • CronJob

下面会分别介绍,先看看Pod与控制器的关系。

Pod 与控制器的关系

首先控制器是在集群上管理和运行容器的对象,也就是管理Pod的,第二控制与pod关联是通过标签(label-selector)进行关联,通过控制器去管理Pod会实现更多流啤的功能,像是什么弹性伸缩升级滚动更新之类的,都是在控制器层面实现的。

Deployment 部署无状态应用

在前面我们使用了Deployment 这个控制器创建了nginx,这种算是无状态的应用,像是nginx根本不用去考虑分配到那个节点或容器IP是多少,所以Deployment 适用于部署无状态的应用,他能管理PodReplicaSet,现在看一下之前的创建的nginxreplicasets

[root@master-1 ~]# kubectl get replicasets
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-55d57dff75   3         3         3       27m

这对于用于来说,这是一个隐藏的控制器,用作管理Pod副本数的,我预期的状态是创建三个副本,可以看一下上文的配置文件,如果有一个pod挂掉了他就会再启动一个pod,会确保有三个在运行,deployment是管理replicasets&pod的,replicasets也做版本的管理,之前我们使用kubectl rollout回滚到上一个版本就是依靠的replicasets

所以总结一下就是deployment能管理PodReplicaSet、具有上线部署、副本设定、滚动更新、回滚等功能,下面编辑一下nginx-deployment的配置文件,看看他的内容,我贴重点。

[root@master-1 ~]# kubectl edit deployments.apps nginx-deployment 
  replicas: 3   ##设置副本数
  selector:
    matchLabels:
      app: nginx    ##标签选择器,pod与控制器关联
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate      ## 滚动更新策略,默认滚动更新

查看回滚版本,也是replicasets来做的。

[root@master-1 ~]# kubectl rollout history deployment nginx-deployment 
deployment.extensions/nginx-deployment 
REVISION  CHANGE-CAUSE
1         <none>

支持声明式更新,例如想更新某一个字段,他就会更新某一个字段,例如更新image字段,deployments比较适合Web服务,之前部署用的就是deployments

有状态和无状态服务区别

刚刚上面提到了deployment部署的是无状态服务,还有一种服务是有状态服务,这俩服务有啥区别,下面分别看一下。

无状态服务概述

deployment认为所有的Pod都是一样的,也就是这些。

[root@master-1 ~]# kubectl get pods 
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-55d57dff75-5rnv9   1/1     Running   0          59m
nginx-deployment-55d57dff75-gm8zm   1/1     Running   0          59m
nginx-deployment-55d57dff75-tcwgm   1/1     Running   0          59m

这三个副本都是一模一样的,都是使用同一个镜像去启动的,里面的数据也是完全一样的,属于是对等关系,而且他们的启动顺序也是没有任何规律,先启动哪个都无所谓,对我应用部署没有任何影响,也不用考虑pod在哪个node运行,更不用也不用考虑Pod:IP是啥子,所以你可以随意扩容和缩容。

有状态服务概述

举一个最简单的例子,例如我现在需要使用三个zookeeper做一个集群,集群最低要求要有三个zookeeper实例,我之前写过swarmzookeeper集群的例子,看过的会发现我每个容器都设置的一个名为ZOO_MY_ID 的全局变量,而且这个值各个容器之间都不一样,有三个容器,我设置的分别是{1..3},各个节点地址都是用的zoo{1..3},这算是唯一的网络标识符,容器挂了重启这个标识符是不变的,但是我那个zookeeper没有挂载数据卷,其实还要有持久存储。

像是这种服务你就不能随意的扩容和缩减了,需要有序进行了,包括扩展删除终止滚动更新服务,像是这种就算有状态服务了,每个实例之间属于不对等关系,各个容器之间有些参数不一致,或是挂载的数据卷源/位置不一致,你不可以随意的扩容或缩减这些服务,否则就会有问题,在K8S中,要实现这种服务就要使用StatefulSet了。

headless service

想知道StatefulSet具体是怎么实现的,要从一个服务谈起,也就是headless service,称为无头服务,回忆一下之前的Service,它是一组pod的访问策略,提供负载均衡和服务发现,访问这个Service他会帮你把请求转发到某个Pod,也就是这个东西,这是之前创建的。

[root@master-1 ~]# kubectl get services nginx-service 
NAME            TYPE       CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
nginx-service   NodePort   10.0.0.43    <none>        80:30001/TCP   16h

类型NodePort,无头服务也是使用了Service,不过在定义的时候不需要CLUSTER-IP了,直接会绑定具体Pod的IP,上面就是一个常规的Service,他关联了三个标签为nginxPod,且提供了一个统一的入口,IP10.0.0.43,你访问这个IP:port他会帮你把请求转发到后端Pod,也就是这里。

[root@master-1 ~]# kubectl get endpoints nginx-service 
NAME            ENDPOINTS                                  AGE
nginx-service   12.13.13.2:80,12.13.2.2:80,12.13.69.2:80   17h

再来看headless service,和常规的稍微有点区别,他会将CLUSTER-IP置位none,找个栗子,官网扒的。

[root@master-1 ~/demo]# cat headless-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx

和正常创建Service的区别就是clusteripnone,默认clusterIP是启用的,现在制定为none了就是不为这个Service分配一个clusterip了,创建一下看效果。

[root@master-1 ~/demo]# kubectl create -f headless-service.yaml 
service/nginx created
[root@master-1 ~/demo]# kubectl get services nginx
NAME    TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
nginx   ClusterIP   None         <none>        80/TCP    6s

可以看到CLUSTER-IPNone了,他没有具体的IP,这种情况下主要用的就是DNS访问了,他保证了唯一的网络标识符,使用IP无法保证你这个pod挂掉重启后永远都是使用这个IP,所以这就是利用了DNS保证了网络唯一标识符的,我们之前部署过了coredns,下面开始部署有状态服务。

部署有状态应用

先把之前的都删了。

[root@master-1 ~/demo]# kubectl delete -f headless-service.yaml 
service "nginx" deleted
[root@master-1 ~/demo]# kubectl delete -f nginx-nfs.yaml 
deployment.apps "nginx-deployment" deleted
service "nginx-service" deleted
[root@master-1 ~/demo]# cat headless-service.yaml > nginx-headless.yaml
[root@master-1 ~/demo]# cat nginx-headless.yaml 
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: nginx
  clusterIP: None
  selector:
    app: nginx

---

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  serviceName: "nginx"
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
          name: nginx

我把ServiceStatefulSet写到了一起,StatefulSetdeployment有差别的地方就是StatefulSet有个serviceName,他是用来关联上面无头service的,上面无头Service的名称就是nginx,直接用这个文件进行部署,部署完之后看一下所创建的StatefulSetService

[root@master-1 ~/demo]# kubectl create -f nginx-headless.yaml 
[root@master-1 ~/demo]# kubectl get services nginx 
[root@master-1 ~/demo]# kubectl get pods

创建好了,现在了解一下唯一的网络标识是怎么来的,就是上图NAME那一行的名称,这就是一个固定的DNS名称了,而且会有有序编号,现在随便启动一个镜像去测试一下。

[root@master-1 ~/demo]# kubectl run -it --image=busybox:1.28.4 --rm --restart=Never sh
/ # for i in 0 1 2
> do 
>  nslookup nginx-$i.nginx
> done
[root@master-1 ~/demo]# kubectl get pods -o wide

没啥子问题, pod名称后需要加上Service的名称才能解析到,这就是一个Pod有一个唯一的域名,这是个固定的,并且解析到自身的IP,所以,总结一下。

StatefulSetdeployment的主要区别就是StatefulSet是有身份的,这个身份有三要素,分别是有自己的域名、主机名、存储(PVC),每一个Pod都有一个唯一的主机名,看一下是啥就知道了。

[root@master-1 ~]# for i in 0 1 2 
> do 
>  kubectl exec nginx-$i hostname 
> done
nginx-0
nginx-1
nginx-2

存储PVC一会会提到,所以你部署类似zookeeker这样的服务就需要用StatefulSet去部署了,而不是deployment,他们之间都是使用dns来维持身份的,过。

DaemonSet

这个比较好理解,他会在你每一个Node上运行一个Pod,新加入的Node也会自动运行一个Pod,比较适合运行采集器之类的东西,后期会涉及到日志采集,用的就是这个,暂时来模拟一下吧,直接帖文件吧。

[root@master-1 ~/demo]# cat daemonset.yaml 
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: filebeat
spec:
  selector:
    matchLabels:
      app: filebeat
  template:
    metadata:
      labels:
        app: filebeat
    spec:
      containers:
      - name: filebeat
        image: nginx:latest
        volumeMounts: 
        - mountPath: /log
          name: varlog
      volumes:
      - name: varlog
        hostPath: 
          path: /var/log
          type: Directory

使用了nginx的镜像,你就把他当成filebeat,这东西是干嘛的应该都了解过,上面把宿主机的/var/log目录挂在到了容器的/log,上面提到了这个类型会在所有的Node节点都启动一个的pod,所以你可以理解为该Pod可以收集宿主机日志,通过filebeat读取挂载目录的日志推到logstash||elasticsearch中,当然现在不做这个,暂时举个栗子来说一下这种类型的应用场景,下面创建一下。

[root@master-1 ~/demo]# kubectl create -f daemonset.yaml 
daemonset.apps/filebeat created
[root@master-1 ~/demo]# kubectl get pods -o wide

可以看到每个节点都有一个在运行,下面去Pod里看看宿主机的东西是否被挂载到Pod,挂在到了就没问题。

[root@master-1 ~/demo]# kubectl exec -it filebeat-7xplh /bin/bash
root@filebeat-7xplh:/# ls /log/

没问题,使用场景就是这样,将来使用filebeat就可以直接采集这个目录下的日志文件了,这个很简单,过。

Job与CronJob

Job分为普通任务(Job)和定时任务(CronJob),普通任务是一次性执行的,定时任务就是有计划的去执行,下面分别去了解一下。

Job

也就是普通任务,下面是一个例子,在官网扒过来的。

[root@master-1 ~/demo]# cat job.yaml 
apiVersion: batch/v1
kind: Job
metadata: 
   name: pi
spec: 
  template: 
    spec: 
      containers: 
      - name: pi
        image: perl
        command: ["perl","-Mbignum=bpi","-wle","print bpi(2000)"]
      restartPolicy: Never
  backoffLimit: 4

使用perl镜像启动一个容器,运行一条命令去计算,这个不用深入了解,重启策略为Never,表示容器退出码为0的情况下从不重启,backoffLimit设置重试的次数,如果这个任务执行失败了怎么办,现在定义了4,如果失败了他会帮你重试4次,为毛要有这个?上面提到了Never表示容器退出码为0的情况下从不重启,如果退出状态码非0还是会重启,如果四次退出码都是0,也就是到达了backoffLimit的限制,他就不会再重启了,默认是6,下面创建一下。

[root@master-1 ~/demo]# kubectl create -f job.yaml 
job.batch/pi created
[root@master-1 ~/demo]# kubectl get jobs.batch 
NAME   COMPLETIONS   DURATION   AGE
pi     1/1           15s        24s
[root@master-1 ~/demo]# kubectl get pod pi-86qdx 
NAME       READY   STATUS      RESTARTS   AGE
pi-86qdx   0/1     Completed   0          26m

可以看到pod的状态为Completed了,说明他执行完命令后正常关闭了,关闭后他不会被删除,自行删除吧,所以这个job比较适合临时跑一个任务,跑完之后就退出了,不再执行了。

CronJob

定时任务,像crontab一样,基于时间去调度,用来做备份还是蛮不错的,下面一个例子,也是在官方扒过来的,顺便改了改。

[root@master-1 ~]# cat demo/cronjob.yaml 
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: rj-bai
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: rj-bai
            image: centos:latest
            command: ["bash","-c","date; echo rj-bai"]
          restartPolicy: OnFailure

schedule为时间表达式,和crontab的表达式写法是一样的,所以这个就是一分钟一次,重启策略为OnFailure,如果容器异常退出就会重启,操作的那条命令都能看懂,下面创建一下,看效果。

[root@master-1 ~/demo]# kubectl create -f cronjob.yaml 
[root@master-1 ~/demo]# kubectl get cronjobs.batch rj-bai 
[root@master-1 ~/demo]# kubectl get pod
[root@master-1 ~/demo]# kubectl logs rj-bai-1553048700-tgzqj 
Wed Mar 20 02:25:05 UTC 2019
rj-bai
[root@master-1 ~/demo]# kubectl get pod
[root@master-1 ~/demo]# kubectl logs rj-bai-1553048760-wrg26 
Wed Mar 20 02:26:05 UTC 2019
rj-bai
[root@master-1 ~/demo]# kubectl get pod
[root@master-1 ~/demo]# kubectl logs rj-bai-1553048820-v9mxd 
Wed Mar 20 02:27:06 UTC 2019
rj-bai
[root@master-1 ~/demo]# kubectl get cronjobs.batch rj-bai

就是这种效果,创建成功后每分钟执行一次,历史默认保留三次,就这样,过。

容器持久化存储

之前使用了volume支持了本地和网络存储,譬如之前的NFS挂载要指定NFS服务器IP要挂载的数据卷和容器要挂载的目录,下面会提到两个东西,分别是PersistenVolume(PV)&PersistentVolumeClaim(PVC),他们作用和设计目的主要是将存储资源可以作为集群的一部分来管理。

PV你可以理解为一个接口,例如挂载NFS,你直接去使用PVC资源去消费就可以了,所以我们要定义PV的大小,以什么样的模式去访问,你知道这些信息就好了,这就是PV&PVC的设计之初,PV的主要作用就是将你后端的存储做一个抽象,然后在集群中可以管理这些存储,它分为静态和动态,静态就是需要手动去除创建,动态就是自动为你去创建一个PV,一会静态和动态都会涉及到。

PVC可以让用户不需要关心具体存储的实现,他只关心你用多大容量,PV也就是持久卷,Pod去使用pv的持久卷,你在任何节点都能访问到自己的数据,即使是删除再启动,PVC可以让用户更简单的使用,成为编排的一部分,所以PV&PVC的关系很简,PV是提供者,提供存储容量,PVC是消费者,消费的过程称为绑定,下面看几个例子。

PV 静态供给

要使用PV会有三个阶段,需要定义容器应用,定义卷的需求,定义数据卷,先看容器定义,就那之前nginx的看看吧,之前有个nginx挂载的是NFS存储,现在在他之前改改,主要就是volumes这一块,我贴重点了。

之前的如下。

[root@master-1 ~/demo]# mkdir pvc
[root@master-1 ~/demo]# cp nginx-nfs.yaml ./pvc/nginx-pvc.yaml
[root@master-1 ~/demo/pvc]# cd ./pvc/ && cat nginx-pvc.yaml  | grep "volumes" -B 3 -A 4
        volumeMounts: 
        - name: wwwroot
          mountPath: /usr/share/nginx/html
      volumes: 
      - name: wwwroot
        nfs: 
          server: 192.168.1.200
          path: /nfs-data

修改后如下。

[root@master-1 ~/demo/pvc]# cat nginx-pvc.yaml  | grep "volumes" -B 3 -A 4
        volumeMounts: 
        - name: wwwroot
          mountPath: /usr/share/nginx/html
      volumes: 
      - name: wwwroot
        persistentVolumeClaim: 
          claimName: wwwroot

定义PVC

现在定义数据卷的来源只需要指定一个PVC,你可以理解这个PVC是个媒人,现在PVC还没有定义,现在定义一下PVC

[root@master-1 ~/demo/pvc]# cat nginx-pvc.yaml | tail -12
--- 

apiVersion: v1
kind: PersistentVolumeClaim
metadata: 
  name: wwwroot
spec: 
  accessModes: 
    - ReadWriteMany
  resources:
    requests:
      storage: 5Gi

目前的访问模式定义的为ReadWriteMany,也就是卷可以由许多节点以读写方式挂载,缩写为RWX ,除了这个还有两种,分别如下。

  • ReadWriteOnce

    • 卷可以由单个节点以读写方式挂载,缩写RWO
  • ReadOnlyMany

    • 卷可以由许多节点以只读方式挂载,缩写ROX

这算是一个需求撒,申请5G的使用空间,访问模式为ReadWriteManypvc需要把这个需求告诉pvpv帮你找到另一半,例如你想找个老婆,这个老婆就是上面的需求,5G的存储空间,访问模式为ReadWriteMany,如果有合适的,pvc就会给你牵线了,从此你就过上没羞没臊的幸福生活了。

定义PV

最后定义一下你的候选老婆,也就是定义数据卷。

[root@master-1 ~/demo/pvc]# cat pv.yaml 
apiVersion: v1
kind: PersistentVolume
metadata: 
  name: nginx-pv
spec: 
  capacity: 
    storage: 5Gi
  accessModes: 
    - ReadWriteMany
  nfs: 
    path: /nfs-data/wwwroot
    server: 192.168.1.200

这里的访问模式和pvc的一样,现在可以开始创建了。

开始创建

需要先创建你的候选老婆(pv),其实在你创建pv的时候你并不知道真的有人需要这个pv,所以一般情况下会创建很多的pv在这里等着,你找老婆的需求发布到pvcpvc会去匹配pv,如果有合适直接牵线,如果没有匹配到你的容器启动就有问题,大概就是这样,所以现在把pv创建了吧。

[root@master-1 ~/demo/pvc]# mkdir /nfs-data/wwwroot
[root@master-1 ~/demo/pvc]# kubectl create -f pv.yaml 
persistentvolume/nginx-pv created
[root@master-1 ~/demo/pvc]# kubectl get pv
NAME       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
nginx-pv   5Gi        RWX            Retain           Available                                   5s

使用get pv可以看到当前集群中哪pv可以用,可以看到就一个刚刚创建的,而且开始可用状态,模式为RWX,你领到后想干嘛干嘛,现在可以创建应用了,找老婆的需求也写好了,所以你没写错的话创建之后就能领走了。

[root@master-1 ~/demo/pvc]# kubectl create -f nginx-pvc.yaml 
[root@master-1 ~/demo/pvc]# kubectl get pod,pv,pvc

没啥子问题,Pod正常启动并且牵线成功,试着访问一下。

[root@master-1 ~/demo/pvc]# echo `date` rj-bai > /nfs-data/wwwroot/index.html
[root@master-1 ~/demo/pvc]# curl https://rj-bai.com
2019年 03月 21日 星期四 18:07:13 CST rj-bai

没问题,这就是使用一个持久卷静态模式下的部署,至于上文中我写的为什么是创建候选老婆,原因是在pvc匹配pv的时候,主要看的是存储容量和访问模式,这两个都符合需求才能匹配成功,都符合两个需求的pv可能有很多,多的话随便给你一个用就算完了,如果匹配不到合适的就绝不会牵线,可能你会感觉很麻烦,每次都要去手动创建pv,能不能我需求发布后自动给我创建一个?可以,这就会涉及到PV的动态供给了,手动创建在小的规模下部署的应用没几个手动搞搞也无所谓,大规模的情况下就很麻烦了,下面看看PV动态供给。

PV动态供给(StorageClass)

K8S提供了一个机制叫Dynamic Provisioning,这个机制工作的核心在于StorageClass,这是一个存储类,他的作用是自动操作你后端的存储,为你创建相对应的PV,一个API对象,下面定义一个StorageClass,后端还使用NFS作为存储。

创建动态供给

还是使用NFS为后端存储,直接在官方扒的文件,我放到了一起,有点长。。。。

[root@master-1 ~/demo/sc]# cat deployment-nfs.yaml 
apiVersion: storage.k8s.io/v1beta1
kind: StorageClass
metadata:
  name: managed-nfs-storage
  annotations:
    storageclass.beta.kubernetes.io/is-default-class: "true"
provisioner: nfs-provisioner

---

apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner

---

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: ["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: 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
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
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: default
roleRef:
  kind: Role
  name: leader-locking-nfs-client-provisioner
  apiGroup: rbac.authorization.k8s.io

---

kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: nfs-client-provisioner
spec:
  replicas: 1
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          image: quay.io/external_storage/nfs-client-provisioner:latest
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: nfs-provisioner
            - name: NFS_SERVER
              value: 192.168.1.200
            - name: NFS_PATH
              value: /nfs-data
      volumes:
        - name: nfs-client-root
          nfs:
            server: 192.168.1.200
            path: /nfs-data

默认情况下NFS是不支持动态供给的,部署了一个插件,你需要改一下NFS地址和共享目录,这个插件需要连接apiserver的,所以要进行授权,上面的文件都已经定义了,下面直接开始创建就行了,确保Pod启动后没有抛错。

[root@master-1 ~/demo/sc]# kubectl create -f deployment-nfs.yaml
[root@master-1 ~/demo/sc]# kubectl get storageclasses.storage.k8s.io
[root@master-1 ~/demo/sc]# kubectl get pod nfs-client-provisioner-6b74dd497f-njw5q 
[root@master-1 ~/demo/sc]# kubectl logs nfs-client-provisioner-6b74dd497f-njw5q 

创建好了,具体这个要怎么使用,下面是一个例子。

使用动态供给

部署一个明月三千里吧,他需要一个持久化的存储和一个唯一的网络标识,现在需要写一个statefulset和一个无头Service,我直接帖文件了。

[root@master-1 ~/demo/sc]# cat mysql-demo.yaml 
apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  ports:
  - port: 3306 
    name: mysql 
  clusterIP: None
  selector:
    app: mysql

---

apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: "mysql"
  template:
    metadata:
      labels:
        app: mysql 
    spec:
      containers:
      - name: mysql
        image: mysql:5.7 
        env: 
        - name: MYSQL_ROOT_PASSWORD
          value: "Sowhat?"
        ports: 
        - containerPort: 3306
        volumeMounts:
        - mountPath: "/var/lib/mysql"
          name: mysql-data
      volumes: 
      - name: mysql-data
        persistentVolumeClaim: 
          claimName: mysql-data

--- 

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-data
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: managed-nfs-storage
  resources:
    requests:
      storage: 5Gi

创建了一个无头ServiceStatefulSet,主要是这里。

      volumes: 
      - name: mysql-data
        persistentVolumeClaim: 
          claimName: mysql-data

数据来源指定的pv动态供给,创建pvc动态供给的内容如下。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-data
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: managed-nfs-storage
  resources:
    requests:
      storage: 5Gi

使用的存储类名称为managed-nfs-storage,就是上面我定义的那个,他和之前的数据卷引用方式差不多,只不过这个需要指定存储类帮你创建pv,也就是这个。

[root@master-1 ~/demo/sc]# kubectl get pod nfs-client-provisioner-6b74dd497f-njw5q 
NAME                                      READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-6b74dd497f-njw5q   1/1     Running   0          3m8s

现在可以创建明月三千里服务了,

[root@master-1 ~/demo/sc]# kubectl create -f mysql-demo.yaml
[root@master-1 ~/demo/sc]# kubectl get pod mysql-0 
NAME      READY   STATUS    RESTARTS   AGE
mysql-0   1/1     Running   0          42s
[root@master-1 ~/demo/sc]# kubectl get pv,pvc

就是这种效果,试试这个明月三千里是否能使用,需要通过DNS名称去访问了,启动一个容器试试。

[root@master-1 ~]# kubectl run -it --image=mysql:5.7 mysql-client --restart=Never --rm /bin/bash
root@mysql-client:/# mysql -uroot -pSowhat? -hmysql-0.mysql 

没问题撒,可以正常访问,最后看一眼NFS上的东西

这只是介绍了一种动态供给方案,还有很多,详见官方文档,这个暂时就过了。

机密存储

包括两个部分,分别是Secret&Configmap,先来扯扯皮,近几天出了点事情,公司停电,导致一物理机当场去世,无法识别硬盘,但是服务器硬盘灯都显示正常,尝试自行修复了一下,但是没啥子卵用,准备去送修了,重点来了,这个物理机宕机导致我集群master-2&node-1&node-2&loadbalancer-1&loadbalancer-2全部去世,集群彻底瘫痪,还好master-1健在,尝试恢复etcd数据失败,没办法只能重建集群了,当然重建之后啥都没了,我次奥,真特么蛋疼,现在是把coredns部署出来了,剩下的暂时不用了,啥时候用到啥时候部署吧,下面开始吧。

Secret

作用是将数据加密并存放到etcd中,让pod容器以挂载volume方式访问,主要的应用场景就是保存凭据,之前其实用过这个了,像是保存私有仓库的凭据信息,还有就是保存证书,用的都是这个,当然这些现在都没了,拉取私有仓库的凭据现在加一下吧,上文用的是yaml文件创建的,下面用命令行试试,之前的名字就是registry-secret,现在还用这个,开撸。

[root@master-1 ~]# kubectl get secrets 
NAME                  TYPE                                  DATA   AGE
default-token-vx289   kubernetes.io/service-account-token   3      15h
[root@master-1 ~]# kubectl create secret generic registry-secret \
> --from-file=.dockerconfigjson=/root/.docker/config.json \
> --type=kubernetes.io/dockerconfigjson
secret/registry-secret created
[root@master-1 ~]# kubectl get secrets registry-secret 
NAME              TYPE                             DATA   AGE
registry-secret   kubernetes.io/dockerconfigjson   1      15s

这样就行了撒,type不要动,文件位置自行指定吧,随便在我私有仓库拉一个mysql:5.7启动试试。

[root@master-1 ~/demo]# cat mysql.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: mysql
spec:
  containers:
  - name: mysql
    image: registry.cn-beijing.aliyuncs.com/rj-bai/mysql:5.7
    env: 
    - name: MYSQL_ROOT_PASSWORD
      value: "Sowhat?"
  imagePullSecrets:
  - name: registry-secret
[root@master-1 ~/demo]# kubectl create -f mysql.yaml
[root@master-1 ~/demo]# kubectl get pod mysql 
NAME    READY   STATUS    RESTARTS   AGE
mysql   1/1     Running   0          45s

没问题,之前yaml文件方式任选其一吧,建议使用yaml文件。

现在存一个用户名密码到secret,然后容器去引用,具体的实现如下。

[root@master-1 ~/demo/secret]# echo rj-bai> user
[root@master-1 ~/demo/secret]# echo Sowhat? > password
[root@master-1 ~/demo/secret]# kubectl create secret generic access --from-file=user --from-file=password 
secret/access  created
[root@master-1 ~/demo/secret]# kubectl get secrets access 
NAME     TYPE     DATA   AGE
access   Opaque   2      13s

创建了一个名为accesssecret,看一下他的数据,都不是明文的。

[root@master-1 ~/demo/secret]# kubectl describe secrets access

这是创建的一种方式,还有就是用yaml创建也可以,但是需要使用base64加密,如下。

[root@master-1 ~/demo/secret]# echo "123456" | base64 
MTIzNDU2Cg==
[root@master-1 ~/demo/secret]# cat passwd.yaml 
apiVersion: v1
kind: Secret
metadata:
  name: passwd
type: Opaque
data:
  password: MTIzNDU2Cg==
[root@master-1 ~/demo/secret]# kubectl create -f passwd.yaml 
secret/passwd created

下面在容器里引用一下,

[root@master-1 ~/demo/secret]# cat nginx.yml 
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
    env:
      - name: USERNAME
        valueFrom:
          secretKeyRef:
            name: access
            key: user
      - name: PASSWORD
        valueFrom:
          secretKeyRef:
            name: access
            key: password
      - name: YAML_PASSWORD
        valueFrom:
          secretKeyRef:
            name: passwd
            key: password

定义了环境变量,变量值来自于secretsecretKeyRef用来引用secretname就是secret的名字,key就是这个

[root@master-1 ~/demo/secret]# kubectl get  secrets access -o yaml
  password: U293aGF0Pwo=
  user: cmotYmFpCg==

下面创建,创建后进入到容器看一下。

[root@master-1 ~/demo/secret]# kubectl create -f nginx.yml 
[root@master-1 ~/demo/secret]# kubectl exec -it nginx /bin/bash
root@nginx:/# env | egrep -i "pass|user"

就是这样撒,这是一种常用的引用方式,还有一种,就是用以volume形式挂载到某个目录下面,先删掉,基于之前的改改。

[root@master-1 ~/demo/secret]# cat nginx.yml 
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - name: access
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: access
    secret:
      secretName: access

数据卷来源就是secret,挂在到了容器的/etc/foo目录,创建后到容器看一下,

[root@master-1 ~/demo/secret]# kubectl create -f nginx.yml 
[root@master-1 ~/demo/secret]# kubectl exec -it nginx /bin/bash
root@nginx:/# ls /etc/foo/
root@nginx:/# cat /etc/foo/{user,password}

使用方法就这两种,定义环境变量用的比较多,secret过。

ConfigMap

他和secret相似,主要存储不需要加密的数据,一般是用来保存配置文件,现在随便保存一个nginx.conf的文件吧,先使用kubectl创建。

[root@master-1 ~]# kubectl create configmap api-proxy --from-file=nginx.conf 
[root@master-1 ~]# kubectl get configmaps api-proxy
[root@master-1 ~]# kubectl describe configmaps api-proxy

都是明文的,具体怎么用,还是有两种办法,一个是以volume的形式挂载进去,还有一个就是变量,既然是配置文件,最好是挂volume了,下面以volume形式挂载到nginx

[root@master-1 ~/demo/configmap]# cat api-proxy.yaml 
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:latest
    volumeMounts: 
    - mountPath: /etc/nginx/
      name: api-proxy
  volumes:
    - name: api-proxy
      configMap: 
        name: api-proxy
[root@master-1 ~/demo/configmap]# kubectl create -f api-proxy.yaml 
pod/nginx created
[root@master-1 ~/demo/configmap]# kubectl get pod
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          38s

数据卷的来源就是configMap,挂载到了/etc/nginx目录下,正常启动了,现在吧他的端口映射出来,能访问apiserver就对了。

[root@master-1 ~/demo/configmap]# kubectl expose pod nginx --port=6443 --target-port=6443 --type=NodePort
service/nginx exposed
[root@master-1 ~/demo/configmap]# kubectl get service
[root@master-1 ~/demo/configmap]# curl -I -k https://node-2:44195

木有问题的撒,就不提用变量的方法了,这个就足够了,过。

安全机制

大概分为三个部分,其一Kubernetes安全框架,其二传输安全,认证。授权准入控制,其三使用RBAC授权,这算是一系列流程,了解一下每个环节都做了哪些操作。

kubernetes安全框架

当你使用kubectl||API||UI实际上就是操作apiserver上的资源,之前创建过Deployment,使用api版本为apps/v1,也就是这个,

apiVersion: apps/v1
kind: Deployment

在你创建的时候apiserver会识别你请求的资源,也就是上面的那两个,如果无法识别直接跑错,识别成功后会经历三个阶段,第一认证,第二授权,第三准入控制,当你发起一个请求需要过了这三步验证K8S才回为你创建资源,如果这三步有任意一步出现问题,那你看到的就是一个失败的结果。

普通用户如果要安全访问集群Apiserver往往需要证书、token、或用户名加密码,token之前配置过,手动生成的一个token,而且指定了一个token文件,也就是这里。

[root@master-1 ~]# cat /usr/local/kubernetes/cfg/kube-apiserver | grep token.csv
--token-auth-file=/usr/local/kubernetes/cfg/token.csv \

这个token文件有一个值是随机生成的,这里就是使用token来认证的,指定了用户,把token对应的用户绑定相对应的权限,那么拿着个token值就有相对应的权限来访问apiserver了。

如果Pod需要访问apiserver,则需要用ServiceAccount去访问,默认就有一个ServiceAccount,查看一下。

[root@master-1 ~]# kubectl get serviceaccounts 
NAME      SECRETS   AGE
default   1         20h

可以通过serveraccountpod中去访问apiserver,说白了就是不同类型的来访问apiserver都会有不同的方式,apiserver在各组件之间起到了协调的作用,和集群访问入口的功能,你想访问集群资源就必须得经过Apiserver

K8S安全控制框架主要是由以下三个阶段进行控制,

  • Authentication
  • Authorrization
  • AdmissionControl

如图所示

每一个阶段都支持插件方式,需要通过API Server配置来启用插件,之前都已经配置过了,现在看一下,例如准入控制

[root@master-1 ~]# cat /usr/local/kubernetes/cfg/kube-apiserver | grep -i Admission
--enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota,NodeRestriction \

授权启用了RBAC

[root@master-1 ~]# cat /usr/local/kubernetes/cfg/kube-apiserver | grep -i Author
--authorization-mode=RBAC,Node \

所以通过apiserver启用不同的插件来使用不同的功能,主要就是这三大块,分别看一下,

Authentication

也就是认证,在认证之前还有个传输,现在已经告别了8080,使用了6443,现在8080已经不对外提供服务了,对外提供服务的是64438080主要是本地master组件来连接使用。

认证方面提供了三种客户端认证方式。

  • HTTPS证书认证:基于CA证书签名的数字证书认证

    • 之前部署K8S时我们生成了N多证书,通过证书内的CN字段就可以识别出您特么是谁。
  • HTTP TOKEN认证: 通过TOKEN来识别用户

    • 这个也用到了,就是在apiserver中配置的那个token文件,那个是做kubelet-bootstrap认证时候用到的,这个用的比较广泛
  • HTTP Base认证: 用户名加密码的方式认证

    • 这种用的就很少了,安全系数比较低。

这是传输和认证层面,第一阶段验证你的身份,你可以理解为这是门禁,你通过工卡进行身份验证,验证通过之后你就能进入到某片区域了。

Authorrization

第二阶段,授权,第一阶段你身份验证通过了,然后进入到了某片区域,但是这片区域有很多房间,具体你能进入到哪个房间就得看你工卡的授权了,你有权限就可以刷开某个门,没权限你就是刷不开,所以这就涉及到授权了,一般用的就是RBAC,基于角色的访问控制,角色就是具体的访问权限的集合,他是负责完成授权工作的。

他去判断你是否有权限去访问某些资源,他会检查以下属性,如图。

在你请求的时候我绝对会携带我要访问哪个资源,下面他就开始检查你有没有权限去访问那个资源,如果没授权的话在这个阶段直接给你拒绝了,你看到的是没有权限访问,所以这就是授权这个阶段。

AdmissionControl

准入控制,前两步认证都过了就该这个了,他实际上是一个准入控制器插件列表,发送到API Server的请求都需要经过这个列表中的每个准入控制器检查,检查不通过,则拒绝请求,啥意思呢,大概是这样,我现在想通过apiserver限制pod资源,但是想限制pod资源需要用到apiserverLimitRanger插件,如果这个插件没启用直接拒绝请求,提示我不支持这个撒,现在这个插件启着呢,所以不会有这个问题,他的工作逻辑就是这样,下面主要看一下RBAC授权。

RBAC授权

也就是上文第二阶段的授权,它允许我们通过API Server来动态修改配置,实时生效,在开始之前先来了解一下RBAC的组成。

RBAC核心概念

  • 角色(Role,ClusterRole)

    • Role:授权特定命名空间的访问权限,K8S逻辑隔离是使用namespaces实现的,它的授权是在命名空间层面的,你能不能访问某个命名空间
    • ClusterRole:此为集群层面的,针对有所的命名空间,
  • 主体(User,Group,ServiceAccount)

    • User: 用户
    • Group: 用户组
    • ServicAccount:服务账号
  • 角色绑定(RoleBinding,ClusterRoleBinding)

    • RoleBinding:将角色绑定到主体,既subject ,它对应Role,创建Role使用这个去绑定,绑定后才会有相对应的权限
    • ClusterRoleBinding: 将集群角色绑定到主体,它对应ClusterRole,创建ClusterRole使用这个去绑定,绑定后才会有相对应的权限

下面两个栗子

RBAC授权普通用户访问命名空间

现在授权一个用户对某个命名空间有读取的权限,说白了就是只读,让你看看就行了,现在随便创建一个命名空间,然后在这个命名空间里启动几个pod

[root@master-1 ~]# kubectl create namespace rj-bai
namespace/rj-bai created
[root@master-1 ~]# kubectl get namespaces rj-bai
NAME          STATUS   AGE
rj-bai        Active   3s
[root@master-1 ~]# kubectl get pod -n rj-bai 
No resources found. 
[root@master-1 ~]# kubectl run nginx --image=nginx --replicas=3 --namespace=rj-bai 
[root@master-1 ~]# kubectl get pod -n rj-bai 
NAME                    READY   STATUS    RESTARTS   AGE
nginx-dbddb74b8-bfj8c   1/1     Running   0          30s
nginx-dbddb74b8-bkbgv   1/1     Running   0          30s
nginx-dbddb74b8-gzkt6   1/1     Running   0          30s

现在新建一个用户rj-bai,他的权限为只读rj-bai命名空间,其他的全部拒绝,首先要创建一个Role角色,需要定义规则了撒,基于官方文档

[root@master-1 ~/demo/rbac]# cat rbac-role.yaml 
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: rj-bai
  name: rj-bai
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list"]

权限为get,watch,list,只读的,创建看一下

[root@master-1 ~/demo/rbac]# kubectl create -f rbac-role.yaml 
role.rbac.authorization.k8s.io/rj-bai created
[root@master-1 ~/demo/rbac]# kubectl get role -n rj-bai 
NAME     AGE
rj-bai   26s

现在有一个角色了,现在有一个rj-bai来了,现在把他绑定到这个角色里。

[root@master-1 ~/demo/rbac]# cat rbac-rolebinding.yaml 
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: read-pods
  namespace: rj-bai
subjects:
- kind: User
  name: rj-bai
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role 
  name: rj-bai
  apiGroup: rbac.authorization.k8s.io

主要两块,命名空间,subjectskind指定为User,不是程序,用户名是rj-bai,在下面就是角色绑定了,指定了类型和名称,也就是上面创建的那个,这就可以了,创建吧。

[root@master-1 ~/demo/rbac]# kubectl create -f rbac-rolebinding.yaml 
rolebinding.rbac.authorization.k8s.io/read-pods created
[root@master-1 ~/demo/rbac]# kubectl get role,rolebinding -n rj-bai 
NAME                                    AGE
role.rbac.authorization.k8s.io/rj-bai   9m8s

NAME                                              AGE
rolebinding.rbac.authorization.k8s.io/read-pods   62s

现在是创建完了,现在还差识别身份,现在使用基于证书的来识别身份,直接上脚本吧。

[root@master-1 ~/demo/rbac]# cat rbac-user.sh 
cat > rj-bai-crs.json <<EOF
{
    "CN": "rj-bai",
    "hosts": [],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "L": "Beijing",
            "ST": "Beijing"
        }
    ]
}
EOF

cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes rj-bai-crs.json | cfssljson -bare rj-bai

kubectl config set-cluster kubernetes \
  --certificate-authority=ca.pem \
  --embed-certs=true \
  --server=https://192.168.1.207:6443 \
  --kubeconfig=rj-bai-kubeconfig

kubectl config set-credentials rj-bai \
  --client-key=rj-bai-key.pem \
  --client-certificate=rj-bai.pem \
  --embed-certs=true \
  --kubeconfig=rj-bai-kubeconfig

kubectl config set-context default \
  --cluster=kubernetes \
  --user=rj-bai \
  --kubeconfig=rj-bai-kubeconfig

kubectl config use-context default --kubeconfig=rj-bai-kubeconfig

rj-bai就是我之前定义的用户名,apiserver我写的是负载均衡地址,会用到apiserver的根证书,所以要复制过来,我写的是相对路径,下面执行一下吧。

[root@master-1 ~/demo/rbac]# cp /usr/local/kubernetes/ssl/ca.pem .
[root@master-1 ~/demo/rbac]# cp /usr/local/kubernetes/ssl/ca-key.pem .
[root@master-1 ~/demo/rbac]# cp /usr/local/kubernetes/ssl/ca-config.json .
[root@master-1 ~/demo/rbac]# sh rbac-user.sh 
[root@master-1 ~/demo/rbac]# ls

已经生成好了,可以去验证了,指定配置文件去连接apiserver即可

[root@master-1 ~/demo/rbac]# kubectl --kubeconfig=rj-bai-kubeconfig get pod -n default
[root@master-1 ~/demo/rbac]# kubectl --kubeconfig=rj-bai-kubeconfig get pod -n rj-bai
[root@master-1 ~/demo/rbac]# kubectl --kubeconfig=rj-bai-kubeconfig get service

只能看到rj-bai命名空间下的pod,什么service之类的都不行,因为没有授权,最简单的一个栗子。

RBAC授权ServiceAccount访问命名空间

之前创建过WEBUI,当然现在没了,去重新创建一下。

[root@master-1 ~]# kubectl create -f /usr/local/kubernetes/soft/kubernetes/cluster/addons/dashboard/dashboard-configmap.yaml 
[root@master-1 ~]# kubectl create -f /usr/local/kubernetes/soft/kubernetes/cluster/addons/dashboard/dashboard-rbac.yaml 
[root@master-1 ~]# kubectl create -f /usr/local/kubernetes/soft/kubernetes/cluster/addons/dashboard/dashboard-secret.yaml 
[root@master-1 ~]# kubectl create -f /usr/local/kubernetes/soft/kubernetes/cluster/addons/dashboard/dashboard-controller.yaml
[root@master-1 ~]# kubectl create -f /usr/local/kubernetes/soft/kubernetes/cluster/addons/dashboard/dashboard-service.yaml

这些完事之后会创建一个ServiceAccount

[root@master-1 ~]# kubectl create -f K8s-admin.yaml 
[root@master-1 ~]# kubectl get secrets -n kube-system dashboard-admin-token-784bs 
NAME                          TYPE                                  DATA   AGE
dashboard-admin-token-784bs   kubernetes.io/service-account-token   3      74s

这个是管理员权限的,用那个token进行登录,登录后能看到所有的东西,上去看一眼。

管理员权限撒,现在创建一个ServiceAccount,只允许访问rj-bai命名空间,权限和上面的一样,首先要创建一个ServiceAccount,然后把这个ServiceAccount绑定到上面创建的角色里。

[root@master-1 ~/demo/sa]# cat sa.yml 
apiVersion: v1
kind: ServiceAccount
metadata: 
  name: rj-bai-account
  namespace: rj-bai

--- 

kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata: 
  name: rj-bai-account
  namespace: rj-bai
subjects: 
- kind: ServiceAccount
  name: rj-bai-account
roleRef:
  kind: Role
  name: rj-bai
  apiGroup: rbac.authorization.k8s.io

就是这样,然后创建吧。

[root@master-1 ~/demo/sa]# kubectl create -f sa.yml
[root@master-1 ~/demo/sa]# kubectl get secrets -n rj-bai rj-bai-account-token-l9n8v
[root@master-1 ~/demo/sa]# kubectl describe secrets -n rj-bai rj-bai-account-token-l9n8v

下面去用这个token登陆一下UI,看看是什么效果。

命名空间需要你手动输入撒,然后会发现各种权限不足,就是这种效果,只读rj-bai命名空间,其他的全部权限不足,这就是RBAC授权,有很多的栗子,在这里,照葫芦画瓢吧,下一篇准备做监控了,具体等到啥子时候就不知道了,有点学不下去了,本篇结束。

最后编辑于: 2019 年 05 月 06 日
返回文章列表 文章二维码 打赏
本页链接的二维码
打赏二维码