k8s部署nginx(Pod、Deployment、Service)

k8s部署nginx(Pod、Deployment、Service),第1张

pod 是 kubernetes 中最小的编排单位,通常由一个容器组成 (有时候会有多个容器组成)

nginx-podyaml

将配置apply到k8s

kubectl apply -f nginxyaml

校验部署状态,此时 STATUS 为 Running 表明部署成功

获取 Pod 部署的状态,特别是 IP ,  -o wide 列出IP/Node等更多信息 

kubectl get pods nginx -o wide

获取更加详细的信息

kubectl describe pod nginx

使用 kubectl exec 进入 Pod 的内部容器。如果 Pod 中有多个容器,使用 kubectl exec -c 指定容器

kubectl exec -it nginx sh

在 Pod 容器中执行命令,校验其中的 socket 情况以及 nginx 服务

netstat -tan

wget -q -O - localhost

二、Deployment

在 k8s 中编排应用可以更好地做d性扩容,负载均衡。既然要均衡,一个 Pod 肯定不能均衡,自然要部署多个 Pod

docker-compose 可以简单地通过 docker-compose scale 来扩容,现在用k8s扩容

在k8s中管理 Pod 的称作 Controller,我们可以使用 Deployment 这种 Controller 来为 Pod 进行扩容,当然它还可以滚动升级,回滚,金丝雀等等关于部署的事情

我们编写一个 Deployment 的资源配置文件

我们使用 kubectl apply 部署生效后查看 Pod 以及 Deployment 状态

kubectl get pods -o wide -l 'app=nginx'

三、 Service 

Service 做服务发现 指定 Deployment 或者特定集合 Pod 的网络层抽象

创建NodePort service时,用户可以指定范围为30000-32767的端口,对该端口的访问就能通过 kube-proxy 代理到service后端的pod中

我们使用 kubectl apply 部署生效后查看 Service 状态

kubectl get svc nginx-service -o wide

curl >

大背景就先不记了(CloudNative、康威定律这些了解一下就好了),先写之前已经学习理解的内容

那就在之前的笔记上面做补充吧

Static pod : 在Node上手动创建的(未通过Master的api server创建的pod),也未存储于etcd当中

endpoint : pod的ip+容器暴露的端口

pause容器 :每一个POD里面都会有一个Pause容器(Pod内所有容器共享Pause容器的ip以及Volume),方便实现容器之间的网络通信以及卷共享,同时Pause容器一般比业务容器要稳定可靠,可以作为Pod的状态标识向Kube-master反映整个Pod的状态

资源: K8s当中管理的对象都叫做K8s的资源,从pod、RC、Server、Node到Cluster(相应的在Yaml或者Json的配置文件当中,根据资源的类型来创建不同的对象)

Replicate描述 :相当于期望Pod的一个运行状态,在其中会定义好Pod的副本数量,Pod使用的容器(容器repository、端口、容器的限额),Pod的label(replicate controller和Service controller后续都会通过label-select来定位到具体的pod进行管理)

也就是说 Service是通过label来关联它的所有POD的

三种IP地址 : Node IP、Cluster IP、Pod IP,其中Node IP即为宿主机的IP地址,就是实际的物理ip地址,Cluster IP为K8s内部为每一个Service分配的唯一虚拟IP地址,Cluster IP无法用来与外部进行网络交互,只能在K8s内部使用;Pod IP为Docker0网桥网段里拿到的ip; 需要注意的是这三种IP在互相交互的时候使用的不是通常的路由规则,而是由K8s内部自定义的规则来进行网络通信的

service关联Pod :在通常情况下,Service当中定义的属性是通过Linux环境变量的方式"注入"到Pod当中的(在容器创建的时候自动引入这些环境变量,主要是Service地址),这些变量已ServiceName_变量属性的方式进行命名;在最新的k8s版本当中,已经引入了DNS来根据Service的名称做Service地址获取(Service域名发现)

NameSpace :在K8s集群当中也有Namespace的概念,其实就是为了方便多租户共同使用同一套K8s集群所设定的规则,所以在创建任何资源的时候都可以带上--namespace标签来定义对应的租户,如果不带标签的话,或将这些资源挂到default namespace下面

APIServer : 本质上是一个Restful Server(对外提供k8s当中的各类资源的各种API),同时将这些资源的信息存储于Master上面的etcd当中;

Controller Manager、Scheduler都是通过访问这些API来知晓当前Node上面的资源的状态的,同时采取相应的管理动作,指的一提的是,apiserver本身也是k8s当中的一个Service,其他进程访问Apiserver相当于访问一个Service

Controller Manager :包含了各种资源、状态的Controller, Replication Controller、Node Controller、ResourceQuota Controller、Namespace、Service Account、Token、Service、Endpoint Controller

Schedule Controller : 接收由Replicate Controller创建的Pod,将其安置到合适的Node,交给后面Node上面的Kubelet来创建实际的POD;

Schedule Controller会按照一定的规则来针对当前集群中的所有Node进行打分(资源消耗最小原则、资源消耗均衡原则、标签优先原则),然后根据打分以及POD定义当中的一些属性来选择出合适的Node

kubelet : Node上面的kubelet有几种方式获取Pod清单:

容器健康管理 : kubelet定期会调用容器的Liveness探针来确定容器的健康状况,liveness探针包括三种方式:

kube-proxy : kube-porxy作为Service的反向代理与负载均衡器,Service外部以Service的Cluster-ip+port或者node ip+Nodeport的方式访问Service业务的时候,这些访问请求会首先被重定向到kube-porxy,然后经过kube-porxy的路由再到达最终的POD

kube-porxy是通过添加iptables来实现重定向的,具体实现是首先kube-porxy会获取一个随机端口作为自己的重定向监听端口,然后添加从外部容器、主机通过Cluster-ip访问的iptables,以及本地的容器、主机通过Nodeport访问的iptables(对于本地容器,重定向目的为kube-porxy的端口,对于外部对象,目的为proxy的ip+proxy的port)

kube-porxy中使用的负载均衡规则为Round Robin算法,除了通常的负载均衡功能之外,还可以配置会话保持(未超时的session一直重定向到同一个Pod),以及配置亲和性(比如按Client ip配置亲和性,让同一个Client ip过来的请求一直访问同一个Pod)

安全管理 :安全管理分几个阶段,首先是认证,再是授权,最后是准入控制;认证阶段有三种方式进行认证,分别是SSL认证(就是>

通常情况下k8s集群中pod使用的是单独划分的一个虚拟子网,处于这个子网中的pod通过nat地址转换的方式访问集群外部。

如果要在集群外部访问pod,通常可以使用三种方式:NodePort,HostPort和LoadBalancer。其中NodePort最为常用,在每个kube-proxy节点上开启代理端口以供外部访问。除此之外有没有别的办法可以在集群直接访问到Pod呢?答案是有的。

假设现在有一套已经搭建好的k8s集群,使用 Calico 做为cni组件,查看每个节点上的路由表

可以看到如下内容

这是calico创建的tunnel,用于实现集群间容器的互相访问,通过这些可以得知每个节点中所划分的pod网段。

我们要做的是就是把这几个路由规则加入到路由器或者客户端机器上

之后我们在k8s中起一个容器做测试,如

再查看已启动的pod

kubectl get pod -o wide

结果如下:

我们在客户端机器上直接访问该pod

ping 102335569

成功响应

踩坑完毕,回到主线。

前面关于port的理解存在偏差,需要用实验来确认port配置的含义。

k8s官方文档对于对于这些配置项的解释还是没有很完善。下面是在其他博文中找到的解释。

已知:

从k8s集群内部的宿主机(物理机、虚拟机)可以直接访问pod的服务地址 ip:80

未知(需要测试):

1、同一局域网内,但没有加入k8s集群的其他服务器能否访问pod的服务地址 ip:80---无法访问

2、能否跳过pod直接访问容器的服务地址 ip:80---没查到ip

首先要知道容器的IP地址

可以看到上面的命令查出的结果是 - 无法看出ip,尝试进入容器查看

然后我就没辙了,不过根据linux系统的精神,所有内容都是文件,但是我google了好久也没找到ip地址到底存在哪一个文件中。然后我就怀疑是不是一定要容器开放端口,ip地址才可以用docker inspect查询,起了一个不开端口的容器,结果也是有ip的。后来问了一个底层开发的朋友,据说ip是不写文件的。

那只能先认为通过k8s启动的容器其实是没有容器ip的。

从侧面看,也很有可能k8s启动的容器确实没有ip

3、访问pod所在的主机的80端口能否返回相同的响应---无法访问

从以上的信息来看,这个port配置应该和docker中暴露端口的意思是一样的,例如下面的例子

来做一下实验:

在我们之前的pod配置文件上增加配置,如下

结果和我们之前的猜测保持一致,增加ports的配置之后,访问宿主机的ip:80也可以访问到pod的内容了。

我这里pod ip 是 101913067,宿主机是 101001237。curl 101913067 和 curl 101001237 得到的结果是一样的。正当我想再仔细看看的时候,服务器又挂了,wc,只能明天找网管重启了。

---第二天

昨天,我还想看看

1、关了这个pod之后是否就不能访问了

启动了2个pod如下,mynginx1没有配置ports,mynginx2配置了ports。

当我关了pod-mynginx2之后访问宿主机101002167应该就不能通了,结果居然是---能访问到!

大吃一惊!结果ip弄错了,宿主机不是101002167,而是101001237,犯了个低级错误。

结果如下:这回和预期的结果终于一样了。

2、宿主机上是不是本身就开启了nginx,所以恰巧就能访问

确认宿主机上没有开启nginx

3、宿主机上的端口开放情况

使用netstat查看宿主机的端口开放,居然没有发现80端口开着,好奇怪。

那如果在101001237宿主机上启动一个nginx端口开在80,结果会是什么样子呢。

我居然启动了,没有端口已被占用的报错。现在把宿主机上的nginx的index页面的内容改一下,看访问101001237:80时,到底访问的是哪一个nginx。

分别从集群内部3台服务器和集群外部1台服务器的机器取访问101001237:80,访问到的都是pod中的nginx。

会不会跟启动顺序有关,因为现在的情况是先启动了pod-nignx,后启动 宿主机-nginx,那现在将pod-nginx关闭,访问101001237:80,看是啥。

集群内部3台服务器和集群外部1台服务器访问101001237:80,结果一致,都是宿主机-nginx。

再启动pod-nginx,查看结果。

访问结果又变回pod-nginx了,4台服务器结果一致。

再去看一下宿主机-nginx的日志,有没有报错信息-----------没有错误日志

现在基本可以得出结论了:当pod和宿主机同时使用某一个端口时,不会因为冲突而报错,但是pod会优先占用端口,而与启动顺序无关。

至于为什么会这样,就不去深究了,毕竟精力有限,作为运维实施,了解到这样子的程度应该够用了。

(1) 通过--from-file参数从 文件 中进行创建,可以 指定key的名称 ,也可以在一个命令行中创建包含多个key的ConfigMap,语法为:

(2)通过--from-file参数从 目录 中进行创建,该目录下的 每个配置文件名 都被设置为 key 文件的内容 被设置为 value ,语法为:

(3)使用--from-literal时会从 文本 中进行创建,直接将指定的key#=value#创建为ConfigMap的内容,语法为:

Downward API可以通过以下 两种方式 Pod信息注入容器内部

(1) 环境变量 :用于 单个变量 ,可以将 Pod信息 Container信息 注入容器内部。

(2) Volume挂载 :将 数组类信息 生成为 文件 挂载到容器内部

注意到上面 valueFrom这种特殊的语法 Downward API 的写法。

目前Downward API提供了 以下变量

Deployment 不足以覆盖所有的应用编排问题,因为在它 看来 ,一个应用的 所有Pod,是完全一样的 ,所以它们之间就没有顺序,也无所谓运行在哪台宿主机上需要时,Deployment就通过Pod模板创建新的Pod,不需要时,就"杀掉"任意一个Pod

但是在实际场景中,并不是所有应用都满足这样的要求比如:主从关系,主备关系,还有就是数据存储类应用,多个实例通常会在本地磁盘上保存一份数据,而这些实例一旦被杀掉,即使重建出来,实例与数据之间的对应关系也已经丢失,从而导致应用失败

这种实例之间有不对等关系,或者 有依赖关系 的应用,被称为"有状态应用"(Stateful Application),为了能对"有状态应用"做出支持,Kubernetes在Deployment基础上,扩展出了:StatefulSet。

StatefulSet将真实世界里的应用状态,抽象为了两种情况:

1, 拓扑状态(顺序编号+固定的网络表示dns) 这种情况是说,应用的多个实例之间不是完全对等的关系这些应用实例,必须按照某些顺序启动,比如某个应用的主节点A要先于B启动,那么当我把A和B两个节点删除之后,重新创建出来时,也要是这个顺序才行并且,新创建出来的A和B,必须和原来的A和B网络标识一样,这样原先的访问者才能使用同样的方法,访问到这个新Pod

2, 存储状态 这种情况是说,应用的多个实例分别绑定了不同的存储数据对于这些应用实例来说,Pod A第一次读取到的数据,和隔了十分钟之后再次读取到的数据,应该是同一份,哪怕在此期间Pod A被重新创建过

所以,StatefulSet的核心功能,就是通过某种方式, 记录这些状态 ,然后在Pod被重新创建时,能够为新Pod恢复这些状态

在深入了解StatefulSet之前,咱们先来讲讲 Headless Service

我们知道,Service是Kubernetes项目中用来将一组Pod暴露给外界访问的一种机制,比如,一个Deployment有3个Pod,那么我就可以定义一个Service,然后用户只要能访问到这个Service,就能访问到某个具体的Pod

但是,这个Service是怎么被访问到的呢

第一种方式,以Service的VIP(Virtual IP,即: 虚拟IP )方式比如:当我访问19216801这个Service的IP地址时,它就是一个VIP在实际中,它会把请求转发到Service代理的具体Pod上

第二种方式,就是以Service的 DNS 方式在这里又分为两种处理方法:第一种是Normal Service这种情况下,当访问DNS记录时,解析到的是Service的VIP第二种是Headless Service这种情况下,访问DNS记录时,解析到的就是某一个Pod的IP地址

可以看到, Headless Service不需要分配一个VIP,而是可以直接以DNS记录的方式解析出被代理Pod的IP地址 这样设计有什么好处呢

service定义时,它的 clusterIP 字段的值是:None,即:这个 Service,没有一个 VIP 作为“头”。这也就是 Headless 的含义。所以,这个 Service 被创建后并不会被分配一个 VIP,而是会以 DNS 记录的方式暴露出它所代理的 Pod。当你按照这样的方式创建了一个 Headless Service 之后,它所代理的所有 Pod 的 IP 地址,都会被绑定一个这样格式的 DNS 记录,如下所示:

<pod-name><svc-name><namespace>svcclusterlocal

这个 DNS 记录,正是 Kubernetes 项目为 Pod 分配的唯一的“可解析身份”(Resolvable Identity)。而有了这个身份之后,只要知道了一个Pod的名字以及它对应的Service的名字,就可以非常确定地通过这条DNS记录访问到Pod的IP地址

介绍完Headless Service之后,咱们再回来讲讲,StatefulSet的核心功能,是如何在Pod被重新创建时,能够为新Pod恢复这些状态

为了详细讲解,现在编写一个StatefulSet的YAML文件,如下:

可以看到,在这个YAML文件中,多了一个 serviceName=nginx 字段这个字段的作用,就是告诉StatefulSet控制器,在执行控制循环时,要使用nginx这个Headless Service来保证Pod的"可解析身份"这样,在创建Pod过程中,StatefulSet给它所管理的所有Pod名字,进行编号,使得每个Pod实例不重复而更重要的是,这些Pod的创建,也是严格按照编号顺序来进行的

StatefulSet 就保证了 Pod 网络标识的稳定性 。

这样的意思就是说,当有主从关系时,有明确先后关系时,StatefulSet通过这种机制,使得先后创建顺序成为可能

通过这种方法, Kubernetes 就成功地将 Pod 的拓扑状态(比如:哪个节点先启动,哪个节点后启动),按照 Pod 的“名字 + 编号”的方式固定了下来 。此外,Kubernetes 还为每一个 Pod 提供了一个固定并且唯一的访问入口,即:这个 Pod 对应的 DNS 记录。

这些状态,在 StatefulSet 的整个生命周期里都会保持不变,绝不会因为对应 Pod 的删除或者重新创建而失效。

不过,相信你也已经注意到了,尽管 web-0nginx 这条记录本身不会变,但它解析到的 Pod 的 IP 地址,并不是固定的。这就意味着,对于“有状态应用”实例的访问,你必须使用 DNS 记录或者 hostname 的方式,而绝不应该直接访问这些 Pod 的 IP 地址。

Kubernetes 中 PVC 和 PV 的设计, 实际上类似于“接口”和“实现”的思想 。开发者只要知道并会使用“接口”,即:PVC;而运维人员则负责给“接口”绑定具体的实现,即:PV。而 PVC、PV 的设计,也使得 StatefulSet 对存储状态的管理成为了可能。 PVC 其实就是一种特殊的 Volume 。只不过一个 PVC 具体是什么类型的 Volume,要在跟某个 PV 绑定之后才知道。当然,PVC 与 PV 的绑定得以实现的前提是,运维人员已经在系统里创建好了符合条件的 PV(比如,我们在前面用到的 pv-volume);或者,你的 Kubernetes 集群运行在公有云上,这样 Kubernetes 就会通过 Dynamic Provisioning 的方式,自动为你创建与 PVC 匹配的 PV。 因为这种方式虽然开发可以直接使用PVC来做存储,但是却需要运维提前定义好PV,包括容量,个数等,这些往往是未知的,所以需要动态存储供应StorageClass 。

通过这种方式,Kubernetes 的 StatefulSet 就实现了对应用存储状态的管理。

看到这里,你是不是已经大致理解了 StatefulSet 的工作原理呢?现在,我再为你详细梳理一下吧。

首先,StatefulSet 的控制器直接管理的是 Pod 。这是因为,StatefulSet 里的不同 Pod 实例,不再像 ReplicaSet 中那样都是完全一样的,而是有了细微区别的。比如,每个 Pod 的 hostname、名字等都是不同的、携带了编号的。而 StatefulSet 区分这些实例的方式,就是通过在 Pod 的名字里加上事先约定好的编号。

其次,Kubernetes 通过 Headless Service,为这些有编号的 Pod,在 DNS 服务器中生成带有同样编号的 DNS 记录 。只要 StatefulSet 能够保证这些 Pod 名字里的编号不变,那么 Service 里类似于 web-0nginxdefaultsvcclusterlocal 这样的 DNS 记录也就不会变,而这条记录解析出来的 Pod 的 IP 地址,则会随着后端 Pod 的删除和再创建而自动更新。这当然是 Service 机制本身的能力,不需要 StatefulSet *** 心。

最后,StatefulSet 还为每一个 Pod 分配并创建一个同样编号的 PVC 。这样,Kubernetes 就可以通过 Persistent Volume 机制为这个 PVC 绑定上对应的 PV,从而保证了每一个 Pod 都拥有一个独立的 Volume。

在这种情况下,即使 Pod 被删除,它所对应的 PVC 和 PV 依然会保留下来。所以当这个 Pod 被重新创建出来之后,Kubernetes 会为它找到同样编号的 PVC,挂载这个 PVC 对应的 Volume,从而获取到以前保存在 Volume 里的数据。

其实StatefulSet就是一种特殊的Deployment,只不过它的每个Pod都被编号了 正是由于这种机制,使得具有主从关系的创建成为可能StatefulSet 其实可以认为是对 Deployment 的改良。与此同时,通过 Headless Service 的方式,StatefulSet 为每个 Pod 创建了一个固定并且稳定的 DNS 记录,来作为它的访问入口。实际上,在部署“有状态应用”的时候,应用的每个实例拥有唯一并且稳定的“网络标识”,是一个非常重要的假设。

本文通过下面的例子,分析访问service ip的流程及iptables规则如何生效。

通过此yaml文件创建三个pod,一个client,两个nginx(监听在80端口),和一个service(将9999映射到nginx的80端口),实现到nginx后端的负载均衡。

查看创建的三个pod,两个nginx pod部署在worker1上,client部署在worker2上。

查看创建的service,可看到对应的两个endpoint。

service创建成功后,会在每个worker node上添加如下iptable规则

有如下三种访问service ip的场景,下面分别验证并分析iptables规则

a 在client pod内部访问

b 在worker node上访问

c 在监听80端口的nginx pod中访问

a 在client pod内部访问

然后进入nat表的处理,在PREROUTING 链上依次查找如下的规则

虽然POSTROUTING链上也有规则,但是都不匹配。

所以查找nat表的结果就是做了dnat。

b 在worker node上访问

然后进入nat表的处理,依次查找如下的规则

在OUTPUT链上,匹配到dnat规则,将数据包的目的ip/port换成了10113984:80或者10113993:80,并且给数据包做了标记0x4000/0x4000。

根据MASQUERADE做snat时,源地址选择可以通过下面命令获取,在此例中,源ip为101236128

c 在nginx pod访问

这里还要再分两种场景,负载均衡后的ip是发起访问的pod和不是发起访问的pod。比如 在nginx1 pod内部访问nginx的service服务,负载均衡后的ip为nginx1 pod的ip,或者为nginx2 pod的ip。

不同pod

在nginx1 pod内部访问nginx的service服务,负载均衡后的ip为nginx2 pod的ip。

将目的ip修改为10113993后,查找路由表时,发现只需要发给本worker上的calic6244c9748e即可。

此场景下的链接跟踪表项

同一个pod

在nginx1 pod内部访问nginx的service服务,负载均衡后的ip为nginx1 pod的ip。和上面的场景的区别是,不只做dnat,还要做snat。

将目的ip修改为10113984后,查找路由表时,发现只需要发给本worker上的calic6244c9748e。

但是在POSTROUTING处,还需要执行如下两条规则,因为数据包已经被打上标记0x4000/0x4000,所以在这里还要执行MASQUERADE,即snat

snat后的源ip可以通过如下命令获取

最后数据包经过dnat和snat后发给给本worker node上的calif67c1668c34。

此场景下的链接跟踪表项

以上就是关于k8s部署nginx(Pod、Deployment、Service)全部的内容,包括:k8s部署nginx(Pod、Deployment、Service)、k8s使用tcpdump到对应的pod、Kubernetes基础知识笔记等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/9681102.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-04-30
下一篇2023-04-30

发表评论

登录后才能评论

评论列表(0条)

    保存