一、简介

  • 服务:一种为一组功能相同的 Pod 提供单一不变的接入点的资源。服务 IP 和端口不会改变
  • 对服务的连接会被路由到提供该服务的任意一个 Pod 上(负载均衡
  • 服务通过标签选择器判断哪些 Pod 属于服务

WHY Service

  • Pod 需要对集群内部其他 Pod 或集群外部客户端 HTTP 请求作出响应
  • Pod 生命周期短,随时启动或关闭。K8s 在 Pod 启动前为其分配 IP 地址,因此客户端不能提前知道 Pod 的 IP 地址
  • 多个 Pod 可能提供相同的服务,因此需要单一的 IP 地址访问

1. 创建服务

kubectl expose ...
apiVersion: v1
kind: Service
metadata:
name: kubia
spec:
selector: # 该服务管理 app=kubia 的 Pod
app: kubia
ports:
- port: 80 # 服务暴露端口
targetPort: 8080 # 服务将连接转发到的容器端口

测试

# 该服务集群 IP 为 10.111.249.153,只能在集群内部访问
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubia ClusterIP 10.111.249.153 <none> 80/TCP 2d16h # 1. 在 K8s 节点 curl
$ curl 10.111.249.153
You've hit kubia-5fje3
# 2. 在运行的 Pod 容器中发送 curl 命令(`--`表示 kubectl 命令的结束,后跟 Pod 内部执行的指令)
$ kubectl exec kubia-7nog1 -- curl -s http://10.111.249.153
You've hit kubia-gzwli

(1) 服务会话亲和性

让同一个 client IP 的请求转发到同一个 Pod

spec:
sessionAffinity: ClientIP # 默认 None(仅支持这两种)

服务会话亲和性不能基于 Cookie

K8s 服务不是在 HTTP 层面上工作。服务处理 TCP 和 UDP 包,并不关心其中的荷载内容。而 cookie 是 HTTP 协议的一部分,服务并不知道它们

会话亲和性和 Web 浏览器

浏览器使用 keep-alive 连接,通过单个连接发送所有请求,而 curl 每次打开一个新连接。服务在连接级别工作,因此当首次与服务连接时会随机,但属于该连接的所有网络数据包全部发送到单个 Pod(即使服务会话亲和性设置为 None),直到连接关闭

(2) 服务暴露多个端口

spec:
ports:
- name: http # 多个端口的服务必须指定端口名字
port: 80
targetPort: 8080
- name: https
port: 443
targetPort: 8443

(3) 使用命名的端口(推荐)

kind: Pod
spec:
containers:
- name: kubia
ports:
- name: http # 端口 8080 被命名为 http
containerPort: 8080
- name: https
containerPort: 8443
kind: Service
spec:
ports:
- name: http
port: 80
targetPort: http # 映射到容器中被称为 http 的端口
- name: https
port: 443
targetPort: https

2. 服务发现

Pod 获取服务 IP 和端口

(1) 通过环境变量发现服务

Pod 启动时,K8s 会初始化一系列环境变量指向现存的服务。若服务早于 Pod 创建,Pod 进程便可根据环境变量获取服务信息

规则:如名为backend-database的服务会生成BACKEND_DATABASE_SERVICE_HOSTBACKEND_DATABASE_SERVICE_PORT两个环境变量

$ kubectl exec kubia-3inly env
KUBIA_SERVICE_HOST=10.111.249.153
KUBIA_SERVICE_PORT=80
...

(2) 通过 DNS 发现服务

kube-system 下的 kube-dns Pod 运行 DNS 服务,集群中的其他 Pod 都被配置成使用其作为 dns(K8s 通过修改每个容器的 /etc/resolve.conf 实现)。因此,运行在 Pod 上的进程 DNS 查询都会被 K8s 自身的 DNS 服务器响应,该服务器知道系统中运行的所有服务

Pod 是否使用内部的 DNS 服务器是根据 Pod 中 spec.dnsPolicy 决定

$ kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 3d20h
$ kubectl exec kubia-3inly -- cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
...

每个服务从内部 DNS 服务器中获得一个 DNS 条目,客户端 Pod 在知道服务名称的情况下可通过 FQDN(全限定域名)来访问。格式为:<服务名称>.<服务命名空间>.svc.cluster.local。其中svc.cluster.local是在所有集群本地服务名称中使用的可配置集群域后缀

客户端仍需知道服务的端口号。服务可直接使用标准端口号(如 HTTP 的 80 端口或 Postgres 的 5432 端口)或从环境变量中获取端口号

$ kubectl exec -it kubia-3inly bash
root@kubia-3inly:/# curl kubia.default.svc.cluster.local
You've hit kubia-3inly
# 若两个 Pod 在同一个命名空间,可直接使用服务名称
root@kubia-3inly:/# curl kubia
You've hit kubia-5asi2
# 服务的集群 IP 为虚拟 IP,且只有与服务端口结合时才有意义
root@kubia-3inly:/# ping kubia
6 packets transmitted, 0 packets received, 100% packet loss

二、连接集群外部的服务

服务将请求重定向到外部 IP 和端口

1. 服务 Endpoint

  • 服务并不是和 Pod 直接相连,而是通过 Endpoint 资源:暴露一个服务的 IP 地址和端口的列表
  • 服务的 Pod 选择器仅用来构建 IP 和端口列表,存储在 Endpoint 资源中。当客户端连接到服务时,服务代理会选择一个 IP 进行重定向
$ kubectl get endpoints kubia
NAME ENDPOINTS AGE
kubia 10.108.1.4:8080,10.108.2.5:8080,10.108.2.6:8080 2h

2. 手动配置服务的 Endpoint

创建不包含 Pod 选择器的服务将不会创建 Endpoint 资源,此时需要手动创建 Endpoint

apiVersion: v1
kind: Service
metadata:
name: external-service
spec: # 没有指定 Pod 选择器
ports:
- port: 80
apiVersion: v1
kind: Endpoints
metadata:
name: external-service # Endpoint 名称需和服务名称匹配
subsets:
- addresses: # 服务将连接重定向到 Endpoint 的 IP 地址
- ip: 11.11.11.11
- ip: 22.22.22.22
ports: # Endpoint 的目标端口
- port: 80

3. 为外部服务创建别名

通过 FQDN(完全限定域名)访问外部服务

apiVersion: v1
kind: Service
metadata:
name: external-service
spec:
type: ExternalName # 创建一个具有别名的外部服务的服务
externalName: someapi.somecompany.com # 实际服务的全限定名
ports:
- port: 80

Pod 通过external-service.default.svc.cluster.local访问外部服务

ExternalName 服务仅在 DNS 级别实施,为服务创建了简单的 CNAME DNS 记录。因此连接到服务的客户端将直接连接到外部服务,完全绕过服务代理。因此该类型服务不会获得集群 IP

CNAME 记录指向完全限定的域名而不是数字 IP 地址

三、将服务暴露给外部客户端

  • 将服务类型设置为 NodePort:每个集群节点打开一个端口,并将在该端口上收到的流量重定向到该服务
  • 将服务类型设置为 LoadBalance:NodePort 类型的一种扩展。服务通过一个专用的负载均衡器来访问,客户端通过负载均衡器的 IP 连接到服务
  • 创建一个 Ingress 资源:通过一个 IP 地址公开多个服务。运行在 HTTP 层(网络协议第七层,而服务运行在第四层)

1. 使用 NodePort 类型的服务

apiVersion: v1
kind: Service
metadata:
name: kubia-nodeport
spec:
type: NodePort # 默认 ClusterIP
ports:
- port: 80
targetPort: 8080
nodePort: 30123 # 集群节点端口(不指定则随机)
selector:
app: kubia

可通过<node-ips>:30123<cluster-ip>:80访问

2. 使用 LoadBalancer 类型的服务

  • 负载均衡器拥有独一无二的可公开访问的 IP 地址,并将连接重定向到服务(节点无需关闭防火墙)
  • 若 K8s 在不支持 LoadBalancer 服务的环境中运行,则不会调用负载均衡器,此时服务仍表现为 NodePort 服务
spec:
type: LoadBalancer

可通过<external-ip>:80访问

3. 了解外部连接的特性

网络跳数

当访问到某个节点的端口,服务随机转发 Pod,此时 Pod 可能不在此节点上,这就需要额外的网络跳转。可将服务配置为仅将外部连接重定向到接收该连接的节点上的 Pod 来阻止跳转:

spec:
externalTrafficPolicy: Local

缺点:

  • 若无本地 Pod 存在,连接将挂起
  • 会导致 Pod 的负载分布不均衡

客户端 IP 不会被记录

当通过节点端口接收到连接时,会对数据包进行 SNAT(源网络地址转换),因此数据包的源 IP 将发生更改

Local 外部流量策略会保留客户端 IP,因为接收连接的节点和 Pod 所在节点没有额外跳跃(不执行 SNAT)

四、通过 Ingress 暴露服务

  • 每个 LoadBalancer 服务都需要自己的负载均衡器以及独有的公有 IP,而 Ingress 只需一个公网 IP 便可为多个服务提供访问
  • 客户端发送 HTTP 请求时,Ingress 会根据请求的主机名和路径决定请求转发到的服务
  • Ingress 在网络栈(HTTP)的应用层,可以提供一些服务不能实现的功能。如基于 cookie 的会话亲和性
  • 只有 Ingress 控制器在集群中运行,Ingress 资源才能正常工作。不同的 K8s 环境使用不同的控制器实现,但有些不提供默认控制器

1. 创建 Ingress 资源

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: kubia
spec:
rules:
# 接收所有请求主机 kubia.example.com 的 HTTP 请求,转发到 kubia-nodeport 的 80 端口
- host: kubia.example.com # must be a DNS name, not an IP address
http:
paths:
- path: /
backend:
serviceName: kubia-nodeport
servicePort: 80
$ kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
kubia <none> kubia.example.com 192.168.99.100 80 14s
# 要将域名解析为 Ingress 控制器的 IP
$ vi /etc/hosts
192.168.99.100 kubia.example.com
$ curl http://kubia.example.com
You've hit kubia-5asi2

2. Ingress 工作原理

  • 客户端首先对 kubia.example.com 执行 DNS 查找,DNS 服务器(或本地操作系统)返回 Ingress 控制器的 IP
  • 客户端向 Ingress 控制器发送 HTTP 请求,并在 Host 头中指定 kubia.example.com
  • 控制器从该头部确定客户端尝试访问哪个服务,通过与服务关联的 EndPoint 查看 Pod IP,并将请求转发给其中一个 Pod

3. 暴露多个服务

将不同的服务映射到不同主机的不同路径

需要将两个域名都指向 Ingress 控制器的 IP 地址,通过 Host 头判断

spec:
rules:
- host: kubia.example.com
http:
paths:
- path: /kubia
backend:
serviceName: kubia
servicePort: 80
- path: /foo
backend:
serviceName: foo
servicePort: 80
- host: bar.example.com
http:
paths:
- path: /
backend:
serviceName: bar
servicePort: 80

4. 处理 TLS 传输

  • Ingress 转发 HTTPS 流量
  • 当客户端创建到 Ingress 控制器的 TLS 连接时,客户端和 Ingress 控制器之间的通信是加密的,而控制器和后端 Pod 之间的通信不是
kubectl create secret tls tls-secret --cert=tls.cert --key=tls.key
kind: Ingress
spec:
tls: # tls 配置
- hosts: # 接收主机的 tls 连接
- kubia.example.com
serviceName: tls-secret # 私钥和证书

五、就绪探针

  • Pod 启动时可能需要加载配置或数据,此时不要将请求转发到这些 Pod,直到准备就绪
  • 就绪探针被定期调用(默认 10s/次),来确定 Pod 是否可以接收客户端请求
  • 启动容器时,可配置一个等待时间,等待后执行第一次就绪检查,之后周期性调用就绪探针
  • 若 Pod 未准备就绪,则从服务中删除该 Pod,就绪后再添加 Pod
  • 只要删除容器,K8s 就会从所有服务中移除该容器,此时无需用就绪探针

类型

  • Exec 探针:由进程的退出状态码确定
  • HTTP GET 探针:向容器发送请求,由响应状态码确定
  • TCP socket 探针:打开一个 TCP 连接到容器的指定端口,由连接是否建立来确定

对比

  • 存活探针通过重启异常容器来保持 Pod 正常工作
  • 就绪探针确保只有准备好的 Pod 才能接收请求

添加就绪探针

apiVersion: v1
kind: ReplicationController
metadata:
name: kubia
spec:
replicas: 2
template:
metadata:
labels:
app: kubia
spec:
containers:
- name: kubia
image: luksa/kubia
readinessProbe:
exec:
command: ["ls", "/var/ready"]
ports:
- containerPort: 8080
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
kubia-5csgl 0/1 Running 0 2m5s
kubia-qj7gz 0/1 Running 0 2m5s
$ kubectl exec kubia-5csgl -- touch /var/ready
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
kubia-5csgl 1/1 Running 0 3m43s
kubia-qj7gz 0/1 Running 0 3m43s

六、headless 服务

创建 headless 服务

apiVersion: v1
kind: Service
metadata:
name: kubia-headless
spec:
clusterIP: None # headless
selector:
app: kubia
ports:
- port: 80
targetPort: 8080

执行 DNS 查找

# 创建可支持 DNS 查找的 Pod
$ kubectl run dnsutils --image=tutum/dnsutils --command -- sleep infinity
pod/dnsutils created
# headless 服务返回的是(就绪的)Pod IP
$ kubectl exec dnsutils nslookup kubia-headless
...
Name: kubia-headless.default.svc.cluster.local
Address: 10.42.0.20
Name: kubia-headless.default.svc.cluster.local
Address: 10.42.0.19
# 常规服务返回的是 Cluster IP
$ kubectl exec dnsutils nslookup kubia
...
Name: kubia.default.svc.cluster.local
Address: 10.43.99.228
  • 客户端也可通过 headless 服务的 DNS 名称直接连接到 Pod
  • headless 服务通过 DNS 轮询机制提供 Pod 的负载均衡,而非服务代理
  • 可通过 DNS 查找机制查找未准备好的 Pod:使用 publishNotReadyAddresses 字段

Kubernetes 实战——发现应用(Service)的更多相关文章

  1. 一文看懂 Kubernetes 服务发现: Service

    Service 简介   K8s 中提供微服务的实体是 Pod,Pod 在创建时 docker engine 会为 pod 分配 ip,"外部"流量通过访问该 ip 获取微服务.但 ...

  2. Kubernetes服务发现之Service详解

    一.引子 Kubernetes Pod 是有生命周期的,它们可以被创建,也可以被销毁,然后一旦被销毁生命就永远结束.通过ReplicationController 能够动态地创建和销毁Pod(列如,需 ...

  3. Kubernetes实战 - 从零开始搭建微服务 - 1.5 提高可用性-发布多节点的Node/Express网络应用程序

    1.5 提高可用性-发布多节点的Node/Express网络应用程序 Kubernetes实战 - 从零开始搭建微服务 前言 在上一篇文章中,已经学习了如何简单地开发一个单层网络应用.[Kuberne ...

  4. 2020 最新 Kubernetes实战指南

    1.Kubernetes带来的变革   对于开发人员 由于公司业务多,开发环境.测试环境.预生产环境和生产环境都是隔离的,而且除了生产环境,为了节省成本,其他环境可能是没有日志收集的,在没有用k8s的 ...

  5. Kubernetes实战总结 - 自定义Prometheus

    一.概述 首先Prometheus整体监控结构略微复杂,一个个部署并不简单.另外监控Kubernetes就需要访问内部数据,必定需要进行认证.鉴权.准入控制, 那么这一整套下来将变得难上加难,而且还需 ...

  6. Kubernetes实战总结 - 阿里云ECS自建K8S集群

    一.概述 详情参考阿里云说明:https://help.aliyun.com/document_detail/98886.html?spm=a2c4g.11186623.6.1078.323b1c9b ...

  7. kubernetes进阶之七:Service

    1.概述 Service也是Kubernetes里的最核心的资源对象之一,Kubernetes里的每个Service其实就是我们经常提起的微服务架构中的一个“微服务”,之前我们所说的Pod.RC等资源 ...

  8. Docker Kubernetes 服务发现原理详解

    Docker Kubernetes  服务发现原理详解 服务发现支持Service环境变量和DNS两种模式: 一.环境变量 (默认) 当一个Pod运行到Node,kubelet会为每个容器添加一组环境 ...

  9. Kubernetes集群中Service的滚动更新

    Kubernetes集群中Service的滚动更新 二月 9, 2017 0 条评论 在移动互联网时代,消费者的消费行为已经“全天候化”,为此,商家的业务系统也要保持7×24小时不间断地提供服务以满足 ...

随机推荐

  1. Windows PE变形练手1-用PE自己的机器码修改自己的逻辑

    PE变形练手1-用PE自己的机器码修改自己的逻辑 就是找一个PE文件,用自己的部分代码部分覆盖或者而修改自己另一个代码部分的补丁姿势(现实中使用很少,极少数破解可以用到.这次例子目的是了解PE). 第 ...

  2. .NET Core with 微服务 - 什么是微服务

    微服务是这几年最流行的架构,说起架构不提微服务都不好意思跟人家打招呼.最近想要再梳理一下关于微服务的知识,并且结合本人的一些实践经验来做一些总结与分享.前面会分享一些概念性的东西,后面也会使用.net ...

  3. Python JWT 介绍

    Python JWT 介绍 目录 Python JWT 介绍 1. JWT 介绍 2. JWT 创建 token 2.1 JWT 生成原理 2.2 JWT 校验 token 原理 3. 代码实现 4. ...

  4. Java初始化数据域的途径

    Java调用构造器的具体处理步骤: 1.所有数据域被初始化为默认值(0,false或null); 2.按照在类声明中出现的次序,依次执行所有域的初始化语句和初始化块: 3.如果构造器第一行调用了第二个 ...

  5. C# 搞桌面UI适配国产麒麟Linux+龙芯遇到的一些坑

    由于一些国企有国产化的需求,所以搞了C#适配银河麒麟,适配了X64和龙芯MIPS版本 1. 在银河麒麟的龙芯版本中 pipe2 不能使用,x64版本上却可以用.  pipe2 用来做自定义消息的,搞U ...

  6. 三分钟了解B2B CRM系统的特点

    最近很多朋友想了解什么是B2B CRM系统,说到这里小Z先来给大家说说什么是B2B--B2B原本写作B to B,是Business-to-Business的缩写.正常来说就是企业与企业之间的生意往来 ...

  7. 企业是否可以用CRM做邮件营销?

    最近总有一些从事外贸,跨境电商的朋友问小Z:"我的企业能用CRM做邮件营销吗?" 我回答:"能,Zoho CRM系统不但能用来发营销邮件,还发得聪明.发得到位." ...

  8. 服务器开发基础-Tcp/Ip网络模型—完成端口(Completion Port)模型

    本文对于初学网络编程的极为友好,文中所有代码全部基于C语言实现,文中见解仅限于作者对于完成端口的初步认识,由于作者才疏学浅,出现的错误和纰漏,麻烦您一定要指出来,咱们共同进步.谢谢!!! 完成端口(c ...

  9. [刷题] 343 Integer Break

    要求 给定一个正数n,可将其分割成多个数字的和,求让这些数字乘积最大的分割方法(至少分成两个数) 示例 n=2,返回1(2=1+1) n=10,返回36(10=3+3+4) 实现 回溯遍历(n^2,超 ...

  10. Jenkins——安装部署

    1.部署Jdk 由于jenkins需要jdk环境,所以先部署jdk,解压并设置环境变量就行: # tar zxf jdk-8u45-linux-x64.tar.gz # mv jdk-8u45-lin ...