上篇文章有提到,经过POD ID只可以在k8s集群内部进行访问,做为一个博客!只给本身看...好像也行啊...
可是每次都要先登陆到集群节点中才能看...这就...(脑海里闪过成吨shell... ssh@192.10o... kubectl get po...,噗~!
这是在玩本身吗?前端
集群都有了,服务也部了,咋就不能让他与万物互联了呢?
接下来咱们就一同探秘,k8s中服务是如何暴露出去的 node
Kubernetes 的Pod的寿命是有限的。它们出生而后死亡,它们不会复活。ReplicationController是特别用来动态的建立和销毁Pods(如:动态伸缩或者执行rolling updates中动态的建立和销毁pod)。尽管每一个Pod有本身的IP地址,随着时间变化随着时间推移即便这些IP地址也不能被认为是可靠的。这带来一个问题:若是一些Pod的集合(让咱们称之为backends)为集群中的其余的Pod提供了一些功能(让咱们称它们为frontends),这些frontends应该如何找到并一直知道哪些backends在这样的集合中呢? nginx
Kubernetes的Service是一种抽象,它定义了一组Pods的逻辑集合和一个用于访问它们的策略 - 有的时候被称之为微服务。
举个例子,想象一下咱们的博客后端运行了三个副本。这些副本都是能够替代的 - 前端不关心它们使用的是哪个后端。尽管实际组成后端集合的Pod可能会变化,前端的客户端却不须要知道这个变化,也不须要本身有一个列表来记录这些后端服务。Service抽象能让你达到这种解耦。git
Kubernetes中的Service是一个REST对象,这点与Pod相似。正如全部的REST对象同样,向apiserver POST一个Service的定义就能建立一个新的实例。以上篇文章中的echoserver为例,每个Pod都开放了80端口,而且都有一个"app=my-blog"的标签。github
{ "kind": "Service", "apiVersion": "v1", "metadata": { "name": "my-blog" }, "spec": { "selector": { "app": "my-blog" }, "ports": [ { "protocol": "TCP", "port": 8080, "targetPort": 80 } ] } }
这个定义会建立一个新的Service对象,名字为”my-blog”,它指向全部带有”app=my-blog”标签的Pod上面的80端口。这个Service同时也会被分配一个IP地址(有时被称做”cluster ip”),它会被服务的代理所使用(见下面)。这个Service的选择器,会不断的对Pod进行筛选,并将结果POST到名字一样为“my-blog”的Endpoints对象。 算法
注意一个Service能将一个来源的端口映射到任意的targetPort。默认状况下,targetPort会被设置成与port字段同样的值。可能更有意思的地方在于,targetPort能够是一个字符串,能引用一个后端Pod中定义的端口名。实际指派给该名称的端口号在每个Pod中可能会不一样。这为部署和更新你的Service提供了很大的灵活性。例如,你能够在你的后端的下一个版本中更改开放的端口,而无需致使客户出现故障。shell
Kubernetes的Service支持TCP和UDP协议。默认是TCP。后端
说到service不得不提 endpoint。在 Service 建立的同时,还生成了一个 Endpoints。 该 Endpoints 与 Service 同名,它所暴露的地址信息正是对应 Pod 的地址。由此猜想是 Endpoints 维护了 Service 与 Pod 的映射关系。
为了验证咱们的猜想,咱们手动删除 Endpoints,发现以前能成功访问到 Pod 的 VIP,如今已经已经访问不到了。api
ServiceController 主要处理的仍是与 LoadBalancer 相关的逻辑,可是 EndpointController 的做用就没有这么简单了,咱们在使用 Kubernetes 时虽然不多会直接与 Endpoint 资源打交道,可是它倒是 Kubernetes 中很是重要的组成部分。浏览器
EndpointController 自己并无经过 Informer 监听 Endpoint 资源的变更,可是它却同时订阅了 Service 和 Pod 资源的增删事件,它会根据 Service 对象规格中的选择器 Selector 获取集群中存在的全部 Pod,并将 Service 和 Pod 上的端口进行映射生成一个 EndpointPort 结构体,对于每个 Pod 都会生成一个新的 EndpointSubset,其中包含了 Pod 的 IP 地址和端口和 Service 的规格中指定的输入端口和目标端口,在最后 EndpointSubset 的数据会被从新打包并经过客户端建立一个新的 Endpoint 资源。因此,除了 Service 的变更会触发 Endpoint 的改变以外,Pod 对象的增删也会触发。
在个人理解,service就是将不断变更的pod作了一个固定的端口映射(别管pod怎么变化,怎么漂移,这是个人事),你只须要处理service暴露出来的固定端口便可。究竟是谁来处理service暴露出来的端口呢?那固然是代理了。
咱们知道 Service 的代理是由 kube-proxy 实现的。而它的代理模式(Proxy mode)主要有两种:userspace 与 iptables。自 K8S v1.2 开始,默认的代理模式就是 iptables(/ipvs),而且它的性能也是要高于 userspace 的
userspace是在用户空间,经过kuber-proxy实现LB的代理服务。这个是kube-proxy的最初的版本,较为稳定,可是效率也天然不过高。
iptables的方式。是纯采用iptables来实现LB。是目前通常kube默认的方式。
ipvs:这种模式从Kubernetes 1.11进入GA,并在Kubernetes 1.12成为kube-proxy的默认代理模式。ipvs模式也是基于netfilter,对比iptables模式在大规模Kubernetes集群有更好的扩展性和性能,支持更加复杂的负载均衡算法(如:最小负载、最少链接、加权等),支持Server的健康检查和链接重试等功能。ipvs依赖于iptables,使用iptables进行包过滤、SNAT、masquared。ipvs将使用ipset须要被DROP或MASQUARED的源地址或目标地址,这样就能保证iptables规则数量的固定,咱们不须要关心集群中有多少个Service了。
咱们如今要作的呢,是将 VIP 请求给转发到对应的 Pod 上。而实现此的正是 iptables(/ipvs)。
举个例子,如今有podA,podB,podC和serviceAB。serviceAB是podA,podB的服务抽象(service)。 那么kube-proxy的做用就是能够将pod(无论是podA,podB或者podC)向serviceAB的请求,进行转发到service所表明的一个具体pod(podA或者podB)上。 请求的分配方法通常分配是采用轮询方法进行分配。
那有了kube-proxy,service是如何暴露的呢?
service的ServiceTypes能让你指定你想要哪种服务。默认的和基础的是ClusterIP,这会开放一个服务能够在集群内部进行链接。NodePort 和LoadBalancer是两种会将服务开放给外部网络的类型。
apiVersion: v1 kind: Service metadata: # Service 实例名称 name: my-blog spec: ports: - protocol: TCP # Service 端口地址 port: 8080 # Pod 端口地址 targetPort: 80 selector: # 匹配符合标签条件的 Pod app: my-blog type: NodePort <- Service Type
ServiceType字段的合法值是:
ClusterIP: 仅仅使用一个集群内部的IP地址 - 这是默认值,在上面已经讨论过。选择这个值意味着你只想这个服务在集群内部才能够被访问到。
NodePort: 在集群内部IP的基础上,在集群的每个节点的端口上开放这个服务。你能够在任意<NodeIP>:NodePort地址上访问到这个服务。
若是定义为NodePort,Kubernetes master 将从给定的配置范围内(默认:30000-32767)分配端口,每一个 Node 将从该端口代理到 Service。该端口将经过 Service 的 spec.ports[*].nodePort 字段被指定。那么咱们就可使用任意节点IP:NodePort来访问到my-blog容器的80端口
LoadBalancer: 在使用一个集群内部IP地址和在NodePort上开放一个服务以外,向云提供商申请一个负载均衡器,会让流量转发到这个在每一个节点上以<NodeIP>:NodePort的形式开放的服务上。
在使用一个集群内部IP地址和在NodePort上开放一个Service的基础上,还能够向云提供者申请一个负载均衡器,将流量转发到已经以NodePort形式开发的Service上。
将上述service文件保存并执行命令
$ kubectl create -f service.yaml service/my-blog created
代表咱们成功建立了一个使用NodePort方式暴露的service
下面咱们来验证一下
经过上第一行命令,咱们能够看到刚刚建立的service已经以NodePort的方式对外暴露(第一个红框),暴露的端口号是32388,经过另外两行命令咱们的项目my-blog部署在哪一台物理机节点上(对应EXTERNAL-IP就是物理机的真实IP),这里惧怕遇到调皮的同窗,因此给它加了个衣服:P
尝试经过IP+Port的方式请求一下
Amazing! 通了!终于不是独乐乐了~
不过... 以用户的习惯,经过IP+端口号来访问的姿式可能没法接受
可能咱们还须要再搞个域名,让用户能够直接经过域名来访问咱们的博客
这时候有些机灵鬼就跳出来了,这还不简单!上面再搭个nginx不就好了...
确实是这样...可是若是我再多部署一个博客呢?(那就再加一条nginx解析记录呗...咱们不是一直这样用的吗???
那...咱们还搞个毛k8s
k8s可不是为了让你部署一两个服务而诞生的...它是为了成千上万个服务~(那你为何在教咱们用k8s部署博客???我:你今天有点话多
若是每个服务都要手动去修改nginx解析记录...你就不怕何时你手多抖两下吗?
总结一下上面service直接暴露服务的一些缺点
而Ingress就是为了解决上面的问题而诞生的
Ingress 的实现分为两个部分 Ingress Controller 和 Ingress .
Ingress Controller 会监听 api server上的 /ingresses 资源 并实时生效。
Ingerss 描述了一个或者多个 域名的路由规则,以 ingress 资源的形式存在。
简单说: Ingress 描述路由规则, Ingress Controller 负责动态实现规则。
下面咱们来动手实现一下ingress,经过动手来学习
首先须要在咱们的集群中安装ingress-nginx-controller
官方文档:https://kubernetes.github.io/...
// 登陆集群,在集群中执行如下命令安装 ingress-nginx kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml
验证:
$ kubectl get pod -n ingress-nginx NAME READY STATUS RESTARTS AGE nginx-ingress-controller-66f7bd6b88-fwshc 1/1 Running 0 8s
说明安装成功
可是官方提供的这个yaml并无指定nginx-ingress-controller本身自己的暴露方式
下面咱们来手动设置以hostNetWork的方式暴露服务
$ kubectl edit deploy nginx-ingress-controller -n ingress-nginx // 修改hostNetWork为true spec.template.spec.hostNetWork: ture deployment.extensions/nginx-ingress-controller edited
修改为功!下面咱们来建立一个ingress资源
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: my-blog namespace: test annotations: nginx.ingress.kubernetes.io/ssl-redirect: "false" // 这个参数标识 不强制要求使用HTTPS协议进行通讯,文章结尾会讲到https的支持 spec: rules: - host: blog.holdno.com http: paths: - path: / backend: serviceName: my-blog servicePort: 8080
经过上面的yaml文件咱们能够很清晰的看出,咱们想要对用户暴露的域名是 blog.holdno.com,解析规则是根路径"/",对应的service为my-blog,转发到service的8080端口(前面咱们service暴露的是8080端口到POD的80端口)
$ kubectl create -f ingress.yaml ingress.extensions/my-blog created $ kubectl get ingress -n test NAME HOSTS ADDRESS PORTS AGE my-blog blog.holdno.com 80 10s
ingress咱们就部署成功了
经过上面两条命令找到nginx-ingress-controller所在的节点的真实IP(在部署ingress时最好是指定某几台性能较好的节点,经过nodeSelector进行调度)
经过域名解析平台将咱们的域名blog.holdno.com解析到节点IP上
curl blog.holdno.com // 下面会出现完美的一幕,你们亲手实践一下吧
到此,咱们便成功的创建了从服务到用户之间的桥梁!以后不管POD怎么漂移,对于上层的ingress都是无谓的
虽然桥通了,可是感受部署一个服务的过程非常心累...
后面的文章,咱们将经过k8s提供的client-go来为你们梳理出一套自动化的部署流程,让咱们的博客能够一键上云!
既然支持http了 天然少不了https的需求
那么咱们就动手一步一步实现 ingress https的支持吧
首先须要了解的是 k8s内置的 secret资源,用来保存证书信息
咱们经过阿里云购买一个免费的证书,并下载到机器上。(假设保存路径为 /ssl/blog.crt 和 /ssl/blog.key)
$ kubectl create secret tls my-secret --cert /ssl/blog.crt --key ./ssl/blog.key --namespace test // 必定要注意,secret是区分namespace的,不一样namespace下的ingress资源和secret资源互相不可见 $ secret/blog created
修改咱们的ingress配置
$ kubectl edit ingress my-blog -n test apiVersion: extensions/v1beta1 kind: Ingress metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"extensions/v1beta1","kind":"Ingress","metadata":{"annotations":{"kubernetes.io/ingress.class":"nginx","nginx.ingress.kubernetes.io/ssl-redirect":"false"},"name":"my-blog","namespace":"test"},"spec":{"rules":[{"host":"blog.holdno.com","http":{"paths":[{"backend":{"serviceName":"my-blog","servicePort":777},"path":"/"}]}}]}} kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/ssl-redirect: "false" // 若是要强制使用https协议,须要将这个参数改成 true creationTimestamp: "2019-09-17T12:20:01Z" generation: 3 name: my-blog namespace: test resourceVersion: "1107154526" selfLink: /apis/extensions/v1beta1/namespaces/test/ingresses/my-blog uid: 78bac7dc-d945-11e9-b8cf-9a285cd2373e spec: rules: - host: blog.holdno.com http: paths: - backend: serviceName: my-blog servicePort: 8080 path: / tls: <- 关键配置 - hosts: - blog.holdno.com # 证书对应的host secretName: blog # 对应的证书 也就是咱们上一步建立的secret status: loadBalancer: {} ingress.extensions/my-blog edited
接下里咱们就能够在浏览器中访问 https://blog.holdno.com
来验证一下。