在 Minikube 中直接上手学习 K8S Service

K8S 为什么需要 Service?

  1. Pod 的 IP 不是固定的;
  2. 一组 Pod 实例之间有负载均衡的需求;

上机操作

以下使用 minikube 作实例:

# 安装
brew install minikube
# 启动
minikube start
# 创建 ~/.kube/config 请注意先备份
minikube update-context
# 进入宿主机
minikube ssh

集群启动后,创建 Deployment 和 Service

apiVersion: v1
kind: Service
metadata:
  namespace: default
  name: nginx-svc
spec:
  type: NodePort
  selector:
    app: nginx
  ports:
  - name: default
    protocol: TCP
    port: 80
    targetPort: 80
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: default
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
          protocol: TCP

这个 Service 使用了 selector 字段来声明这个 Service 只代理携带了 app=nginx 标签的 Pod。并且,这个 Service 的 80 端口,代理的是 Pod 的 8080 端口。

> kubectl get endpoints nginx-svc

NAME        ENDPOINTS                                      AGE
nginx-svc   10.244.0.13:80,10.244.0.14:80,10.244.0.15:80   4m29s

只有处于 Running 状态,且 readinessProbe 检查通过的 Pod,才会出现在 Service 的 Endpoints 列表里。并且,当某一个 Pod 出现问题时,Kubernetes 会自动把它从 Service 里摘除掉。

> kubectl get svc nginx-svc

NAME        TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
nginx-svc   NodePort   10.110.83.228   <none>        80:32597/TCP   4m37s

这个ip是 k8s 自动为 Service 分配的。Service 提供的是 Round Robin 方式的负载均衡。可以使用以下方式进行测试

> minikube ip
192.168.49.2

> minikube ssh
curl http://192.168.49.2:32597

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Service 工作原理

Service 是由 kube-proxy 组件,加上 iptables 来共同实现的。

  1. 凡是目的地址是 10.110.83.228、目的端口是 80 的 IP 包,都应该跳转到另外一条名叫 KUBE-SVC-KBEH26U7YV6VBLSX 的 iptables 链进行处理。使用 iptables-save 查看
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/nginx-svc:default" -m tcp --dport 32597 -j KUBE-EXT-KBEH26U7YV6VBLSX

-A KUBE-SERVICES -d 10.110.83.228/32 -p tcp -m comment --comment "default/nginx-svc:default cluster IP" -m tcp --dport 80 -j KUBE-SVC-KBEH26U7YV6VBLSX
  1. KUBE-SVC-KBEH26U7YV6VBLSX 是一组规则,这三个目的地就是Service 代理的 3 个pod。
-A KUBE-SVC-KBEH26U7YV6VBLSX -m comment --comment "default/nginx-svc:default -> 10.244.0.13:80" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-E36YYYCTO5EK2IJ4
-A KUBE-SVC-KBEH26U7YV6VBLSX -m comment --comment "default/nginx-svc:default -> 10.244.0.14:80" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-INUGID7JOCIXUMGA
-A KUBE-SVC-KBEH26U7YV6VBLSX -m comment --comment "default/nginx-svc:default -> 10.244.0.15:80" -j KUBE-SEP-H22OXDXNEK3GBZK4

iptables 规则的匹配是从上到下逐条进行的,所以为了保证上述三条规则每条被选中的概率都相同,我们应该将它们的 probability 字段的值分别设置为 1/3(0.333…)、1/2 和 1。这种设计思路,在我们日常开发中,也是值的借鉴的。Endpoints 对应的 iptables 规则,正是 kube-proxy 通过监听 Pod 的变化事件,在宿主机上生成并维护的。缺点:宿主机有大量Pod时,大量 iptables 规则不断被刷新,占用该宿主机 CPU 资源。

IPVS 模式

IPVS 模式的工作原理,其实跟 iptables 模式类似。当我们创建了前面的 Service 之后,kube-proxy 首先会在宿主机上创建一个虚拟网卡(叫作:kube-ipvs0),并为它分配 Service VIP 作为 IP 地址按如文档方法开启 ipvs。Minikube 操作开启 IPVS

# 安装 ipvsadm 工具
sudo apt-get update -y
sudo apt-get install -y ipvsadm

准备就绪后,重新创建 nginx-svc。kube-proxy 就会通过 Linux 的 IPVS 模块,为这个 IP 地址设置三个 IPVS 虚拟主机,并设置这三个虚拟主机之间使用轮询模式 (rr) 来作为负载均衡策略。

kubectl get svc nginx-svc
NAME        TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
nginx-svc   NodePort   10.97.127.252   <none>        80:31379/TCP   3m3s
ipvsadm -ln

TCP  10.97.127.252:80 rr
  -> 10.244.0.13:80               Masq    1      0          0
  -> 10.244.0.14:80               Masq    1      0          0
  -> 10.244.0.15:80               Masq    1      0          0

相比于 iptables,IPVS 在内核中的实现其实也是基于 Netfilter 的 NAT 模式,所以在转发这一层上,理论上 IPVS 并没有显著的性能提升。但是,IPVS 并不需要在宿主机上为每个 Pod 设置 iptables 规则,而是把对这些“规则”的处理放到了内核态,从而极大地降低了维护这些规则的代价。“将重要操作放入内核态”是提高性能的重要手段。不过需要注意的是,IPVS 模块只负责上述的负载均衡和代理功能。而一个完整的 Service 流程正常工作所需要的包过滤、SNAT 等操作,还是要靠 iptables 来实现。只不过,这些辅助性的 iptables 规则数量有限,也不会随着 Pod 数量的增加而增加。所以,在大规模集群里,我非常建议你为 kube-proxy 设置–proxy-mode=ipvs 来开启这个功能。它为 Kubernetes 集群规模带来的提升,还是非常巨大的。

Service 与 DNS

在 Kubernetes 中,Service 和 Pod 都会被分配对应的 DNS A 记录(从域名解析 IP 的记录)。再次创建了 ClusterIP 模式 service(type: ClusterIP) 和 Headless Service(.spec.clusterIP: None)

> kubectl get svc
NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
nginx-svc              NodePort    10.97.127.252   <none>        80:31379/TCP   11h
nginx-svc-cluster-ip   ClusterIP   10.106.66.77    <none>        80/TCP         23m
nginx-svc-headless     ClusterIP   None            <none>        80/TCP         19m

对于 CLUSTER-IP 有值的,不管是 NodePort 还是 ClusterIP 类型,Service 的 A记录格式都是: ..svc.cluster.local而对于指定了 clusterIP=None 的 Headless Service 来说,它的 A 记录的格式也是:..svc.cluster.local。但是,当你访问这条 A 记录的时候,它返回的是所有被代理的 Pod 的 IP 地址的集合。我们登上一个 pod 看一看,首先需要安装 nslookup

kubectl exec nginx-7c5ddbdf54-gg5ql -it /bin/bash
# 安装 nslookup
apt update && apt install -y dnsutils

> nslookup nginx-svc
Server:                10.96.0.10
Address:        10.96.0.10#53

Name:        nginx-svc.default.svc.cluster.local
Address: 10.97.127.252

> nslookup nginx-svc-cluster-ip
Server:                10.96.0.10
Address:        10.96.0.10#53

Name:        nginx-svc-cluster-ip.default.svc.cluster.local
Address: 10.106.66.77

> nslookup nginx-svc-headless
Server:                10.96.0.10
Address:        10.96.0.10#53

Name:        nginx-svc-headless.default.svc.cluster.local
Address: 10.244.0.13
Name:        nginx-svc-headless.default.svc.cluster.local
Address: 10.244.0.14
Name:        nginx-svc-headless.default.svc.cluster.local
Address: 10.244.0.15

附录 Service 类型

在 Kubernetes 中,Service 类型用于将一组 Pod 暴露为网络服务。Service 类型有四种:

  • ClusterIP: 这是默认的 Service 类型。ClusterIP 服务具有内部 IP 地址,只能由集群内部的其他 Pod 访问。
  • NodePort: NodePort 将服务暴露在每个节点的外部 IP 地址上,并使用指定的端口。任何人都可以使用该端口从集群外部访问服务。
  • LoadBalancer: LoadBalancer 服务使用云提供商的负载均衡器将服务暴露在互联网上。
  • ExternalName: ExternalName 服务指向外部 DNS 名称。

ClusterIPNodePort 的主要区别在于 可访问性:

  • ClusterIP: 仅限于集群内部访问,外部无法访问。适合内部服务或测试环境。
  • NodePort: 可以从集群外部通过 Node 的 IP 地址和端口访问。适合需要从外部访问的服务,例如 Web 服务或 API。

默认值:默认情况下,Kubernetes 服务的类型为 ClusterIP。这意味着如果您不指定 service.spec.type 字段,则将创建 ClusterIP 服务。

Read more

代码 Refactoring

重构不必谈之色变。 它也不是洪水猛兽,而是开发过程中持续进行的优化过程。让我们开始学习这个主题,重新认识它的价值。 🌟整洁代码 重构的主要目的是消除技术债务。它将混乱变成整洁的代码和简单的设计。 * 对于其他程序员来说,整洁的代码是显而易见的。 我并不是在谈论超级复杂的算法。糟糕的变量命名、臃肿的类和方法、魔法数字 - 等等,所有这些都会让代码变得混乱和难以理解。 * 整洁的代码不包含重复。 每次你需要对重复的代码进行更改时,你都必须记住对每个实例进行相同的更改。这会增加认知负担并减慢进度。 * 整洁的代码包含最少数量的类和其他活动部件。 代码越少,脑子里需要记住的东西就越少。代码越少,维护工作就越少。代码越少,错误就越少。代码就是责任,保持代码简短。 * 整洁的代码通过所有测试。 如果只有 95% 的测试通过,你就知道代码不整洁。如果测试覆盖率为 0%,你就知道你完蛋了。 * 整洁的代码维护成本低! 🗑️技术债(What) 每个人都尽最大努力从头开始编写出色的代码。可能没有程序员会故意编写不干净的代码,从而损害项目。但是干净的代码在什么时

By brian

CSV 格式说明和应用

问题 我们常常将多个字符串item使用逗号拼接成一个字符串,用来表示数组,使用时再用逗号切割成为数组。比如安卓机型列表: ALN-AL10,ALN-AL10,BRA-AL00,ALN-AL00/ALN-AL80 直到有一天,苹果设备也要用到这个机型列表,而它的每个机型都带着逗号,那我们使用逗号切割就得到了错误的数据。 iPhone15: iPhone15,4 iPhone15Plus: iPhone15,5 iPhone15Pro: iPhone16,1 iPhone15Pro_Max: iPhone16,2 为了解决这个问题,首先想到了换一个分隔符,比如 | ,再比如用一些不可见字符 : 0x01。 但我们不能保证这些字符串 item 一定不包含这些特殊字符,也许还有更好的方法。 既然是逗号分隔,首先想到的就是 CSV格式,毕竟 CSV 的全称就是Comma-Separated Values逗号分隔值。它是如何解决这个问题的? CSV格式 CSV 的RFC说明文档:https://datatracker.ietf.

By brian
开启了http2,但是http2_max_field_size 还在用默认值吗?

开启了http2,但是http2_max_field_size 还在用默认值吗?

排查1个异常接口,学到一个降本和接口提速的新思路。另外找到1个charles的"bug" 现象 测试同学反馈在iOS13设备上请求接口报错,将请求复制为 curl 命令。分别导入 apifox 和 在终端执行: * 在 apifox 请求正常 ✅ * 在终端请求失败 ❌ curl: (56) Failure when receiving data from the peer 测试同学又反馈另一个手机支持请求接口返回正常。 定位 有了正常和错误的请求curl,那直接对比二者差异就很简单了。对比发现,在终端执行失败的请求中多了一个较大的Cookie: app_token。按历史经验基本能确定是因为 Cookie 这个 header 条目太大,超过服务器的限制。 找云平台确定相关配置: ELB http1: header头限制 128KB,body 限制一个10G http2:

By brian
沪ICP备2022013452号-1