【重识云原生】第六章容器基础6.4.9.2节——使用 Service 连接到应用

2022-11-18 19:34:48 浏览数 (1)

   《重识云原生系列》专题索引:

  1. 第一章——不谋全局不足以谋一域
  2. 第二章计算第1节——计算虚拟化技术总述
  3. 第二章计算第2节——主流虚拟化技术之VMare ESXi
  4. 第二章计算第3节——主流虚拟化技术之Xen
  5. 第二章计算第4节——主流虚拟化技术之KVM
  6. 第二章计算第5节——商用云主机方案
  7. 第二章计算第6节——裸金属方案
  8. 第三章云存储第1节——分布式云存储总述
  9. 第三章云存储第2节——SPDK方案综述
  10. 第三章云存储第3节——Ceph统一存储方案
  11. 第三章云存储第4节——OpenStack Swift 对象存储方案
  12. 第三章云存储第5节——商用分布式云存储方案
  13. 第四章云网络第一节——云网络技术发展简述
  14. 第四章云网络4.2节——相关基础知识准备
  15. 第四章云网络4.3节——重要网络协议
  16. 第四章云网络4.3.1节——路由技术简述
  17. 第四章云网络4.3.2节——VLAN技术
  18. 第四章云网络4.3.3节——RIP协议
  19. 第四章云网络4.3.4.1-2节——OSPF协议综述
  20. 第四章云网络4.3.4.3节——OSPF协议工作原理
  21. 第四章云网络4.3.4.4节——[转载]OSPF域内路由
  22. 第四章云网络4.3.4.5节——[转载]OSPF外部路由
  23. 第四章云网络4.3.4.6节——[转载]OSPF特殊区域之Stub和Totally Stub区域详解及配置
  24. 第四章云网络4.3.4.7节——OSPF特殊区域之NSSA和Totally NSSA详解及配置
  25. 第四章云网络4.3.5节——EIGRP协议
  26. 第四章云网络4.3.6节——IS-IS协议
  27. 第四章云网络4.3.7节——BGP协议
  28. 第四章云网络4.3.7.2节——BGP协议概述
  29. 第四章云网络4.3.7.3节——BGP协议实现原理
  30. 第四章云网络4.3.7.4节——高级特性
  31. 第四章云网络4.3.7.5节——实操
  32. 第四章云网络4.3.7.6节——MP-BGP协议
  33. 第四章云网络4.3.8节——策略路由
  34. 第四章云网络4.3.9节——Graceful Restart(平滑重启)技术
  35. 第四章云网络4.3.10节——VXLAN技术
  36. 第四章云网络4.3.10.2节——VXLAN Overlay网络方案设计
  37. 第四章云网络4.3.10.3节——VXLAN隧道机制
  38. 第四章云网络4.3.10.4节——VXLAN报文转发过程
  39. 第四章云网络4.3.10.5节——VXlan组网架构
  40. 第四章云网络4.3.10.6节——VXLAN应用部署方案
  41. 第四章云网络4.4节——Spine-Leaf网络架构
  42. 第四章云网络4.5节——大二层网络
  43. 第四章云网络4.6节——Underlay 和 Overlay概念
  44. 第四章云网络4.7.1节——网络虚拟化与卸载加速技术的演进简述
  45. 第四章云网络4.7.2节——virtio网络半虚拟化简介
  46. 第四章云网络4.7.3节——Vhost-net方案
  47. 第四章云网络4.7.4节vhost-user方案——virtio的DPDK卸载方案
  48. 第四章云网络4.7.5节vDPA方案——virtio的半硬件虚拟化实现
  49. 第四章云网络4.7.6节——virtio-blk存储虚拟化方案
  50. 第四章云网络4.7.8节——SR-IOV方案
  51. 第四章云网络4.7.9节——NFV
  52. 第四章云网络4.8.1节——SDN总述
  53. 第四章云网络4.8.2.1节——OpenFlow概述
  54. 第四章云网络4.8.2.2节——OpenFlow协议详解
  55. 第四章云网络4.8.2.3节——OpenFlow运行机制
  56. 第四章云网络4.8.3.1节——Open vSwitch简介
  57. 第四章云网络4.8.3.2节——Open vSwitch工作原理详解
  58. 第四章云网络4.8.4节——OpenStack与SDN的集成
  59. 第四章云网络4.8.5节——OpenDayLight
  60. 第四章云网络4.8.6节——Dragonflow
  61. 第四章云网络4.9.1节——网络卸载加速技术综述
  62. 第四章云网络4.9.2节——传统网络卸载技术
  63. 第四章云网络4.9.3.1节——DPDK技术综述
  64. 第四章云网络4.9.3.2节——DPDK原理详解
  65. 第四章云网络4.9.4.1节——智能网卡SmartNIC方案综述
  66. 第四章云网络4.9.4.2节——智能网卡实现
  67. 第六章容器6.1.1节——容器综述
  68. 第六章容器6.1.2节——容器安装部署
  69. 第六章容器6.1.3节——Docker常用命令
  70. 第六章容器6.1.4节——Docker核心技术LXC
  71. 第六章容器6.1.5节——Docker核心技术Namespace
  72. 第六章容器6.1.6节—— Docker核心技术Chroot
  73. 第六章容器6.1.7.1节——Docker核心技术cgroups综述
  74. 第六章容器6.1.7.2节——cgroups原理剖析
  75. 第六章容器6.1.7.3节——cgroups数据结构剖析
  76. 第六章容器6.1.7.4节——cgroups使用
  77. 第六章容器6.1.8节——Docker核心技术UnionFS
  78. 第六章容器6.1.9节——Docker镜像技术剖析
  79. 第六章容器6.1.10节——DockerFile解析
  80. 第六章容器6.1.11节——docker-compose容器编排
  81. 第六章容器6.1.12节——Docker网络模型设计
  82. 第六章容器6.2.1节——Kubernetes概述
  83. 第六章容器6.2.2节——K8S架构剖析
  84. 第六章容器6.3.1节——K8S核心组件总述
  85. 第六章容器6.3.2节——API Server组件
  86. 第六章容器6.3.3节——Kube-Scheduler使用篇
  87. 第六章容器6.3.4节——etcd组件
  88. 第六章容器6.3.5节——Controller Manager概述
  89. 第六章容器6.3.6节——kubelet组件
  90. 第六章容器6.3.7节——命令行工具kubectl
  91. 第六章容器6.3.8节——kube-proxy
  92. 第六章容器6.4.1节——K8S资源对象总览
  93. 第六章容器6.4.2.1节——pod详解
  94. 第六章容器6.4.2.2节——Pod使用(上)
  95. 第六章容器6.4.2.3节——Pod使用(下)
  96. 第六章容器6.4.3节——ReplicationController
  97. 第六章容器6.4.4节——ReplicaSet组件
  98. 第六章容器基础6.4.5.1节——Deployment概述
  99. 第六章容器基础6.4.5.2节——Deployment配置详细说明
  100. 第六章容器基础6.4.5.3节——Deployment实现原理解析
  101. 第六章容器基础6.4.6节——Daemonset
  102. 第六章容器基础6.4.7节——Job
  103. 第六章容器基础6.4.8节——CronJob
  104. 第六章容器基础6.4.9.1节——Service综述
  105. 第六章容器基础6.4.9.2节——使用 Service 连接到应用
  106. 第六章容器基础6.4.9.3节——Service拓扑感知
  107. 第六章容器基础6.4.10.1节——StatefulSet概述
  108. 第六章容器基础6.4.10.2节——StatefulSet常规操作实操
  109. 第六章容器基础6.4.10.3节——StatefulSet实操案例-部署WordPress 和 MySQL
  110. 第六章容器基础6.4.10.4节——StatefulSet实操案例-使用 StatefulSet 部署Cassandra
  111. 第六章容器基础6.4.10.5节——Statefulset原理剖析
  112. 第六章容器基础6.4.11.1节——Ingress综述

1 使用 Service 连接到应用实操

1.1 Kubernetes 连接容器的模型

        既然有了一个持续运行、可复制的应用,我们就能够将它暴露到网络上。

        Kubernetes 假设 Pod 可与其它 Pod 通信,不管它们在哪个主机上。 Kubernetes 给每一个 Pod 分配一个集群私有 IP 地址,所以没必要在 Pod 与 Pod 之间创建连接或将容器的端口映射到主机端口。 这意味着同一个 Pod 内的所有容器能通过 localhost 上的端口互相连通,集群中的所有 Pod 也不需要通过 NAT 转换就能够互相看到。 本文档的剩余部分详述如何在上述网络模型之上运行可靠的服务。

        本节使用一个简单的 Nginx 服务器来演示概念验证原型。

1.2 在集群中暴露 Pod

        我们在之前的示例中已经做过,然而让我们以网络连接的视角再重做一遍。 创建一个 Nginx Pod,注意其中包含一个容器端口的规约:service/networking/run-my-nginx.yaml

代码语言:javascript复制
apiVersion: apps/v1 
kind: Deployment 
metadata: 
  name: my-nginx 
spec: 
  selector: 
    matchLabels: 
      run: my-nginx 
  replicas: 2 
  template: 
    metadata: 
      labels: 
        run: my-nginx 
    spec: 
      containers: 
        - name: my-nginx 
          image: nginx 
          ports: 
            - containerPort: 80

        这使得可以从集群中任何一个节点来访问它。检查节点,该 Pod 正在运行:

代码语言:javascript复制
kubectl apply -f ./run-my-nginx.yaml 
kubectl get pods -l run=my-nginx -o wide

NAME                      READY STATUS  RESTARTS AGE     IP             NODE 
my-nginx-3800858182-jr4a2  1/1  Running    0     13s 10.244.3.4 kubernetes-minion-905m 
my-nginx-3800858182-kna2y  1/1  Running    0     13s 10.244.2.5 kubernetes-minion-ljyd

检查 Pod 的 IP 地址:

代码语言:javascript复制
kubectl get pods -l run=my-nginx -o custom-columns=POD_IP:.status.podIPs 
    POD_IP 
    [map[ip:10.244.3.4]] 
    [map[ip:10.244.2.5]]

        你应该能够通过 ssh 登录到集群中的任何一个节点上,并使用诸如 curl 之类的工具向这两个 IP 地址发出查询请求。 需要注意的是,容器 不会 使用该节点上的 80 端口,也不会使用任何特定的 NAT 规则去路由流量到 Pod 上。 这意味着可以在同一个节点上运行多个 Nginx Pod,使用相同的 containerPort,并且可以从集群中任何其他的 Pod 或节点上使用 IP 的方式访问到它们。 如果你想的话,你依然可以将宿主节点的某个端口的流量转发到 Pod 中,但是出于网络模型的原因,你不必这么做。

        如果对此好奇,请参考 Kubernetes 网络模型。

1.3 创建 Service

        我们有一组在一个扁平的、集群范围的地址空间中运行 Nginx 服务的 Pod。 理论上,你可以直接连接到这些 Pod,但如果某个节点死掉了会发生什么呢? Pod 会终止,Deployment 将创建新的 Pod,且使用不同的 IP。这正是 Service 要解决的问题。

        Kubernetes Service 是集群中提供相同功能的一组 Pod 的抽象表达。 当每个 Service 创建时,会被分配一个唯一的 IP 地址(也称为 clusterIP)。 这个 IP 地址与 Service 的生命周期绑定在一起,只要 Service 存在,它就不会改变。 可以配置 Pod 使它与 Service 进行通信,Pod 知道与 Service 通信将被自动地负载均衡到该 Service 中的某些 Pod 上。

        可以使用 kubectl expose 命令为 2个 Nginx 副本创建一个 Service:

代码语言:javascript复制
kubectl expose deployment/my-nginx service/my-nginx exposed

        这等价于使用 kubectl create -f 命令及如下的 yaml 文件创建:service/networking/nginx-svc.yaml

代码语言:javascript复制
apiVersion: v1 
kind: Service 
metadata: 
  name: my-nginx 
  labels: 
    run: my-nginx 
spec: 
  ports: 
    - port: 80 
      protocol: TCP 
  selector: 
    run: my-nginx

        上述规约将创建一个 Service,该 Service 会将所有具有标签 run: my-nginx 的 Pod 的 TCP 80 端口暴露到一个抽象的 Service 端口上(targetPort:容器接收流量的端口;port:可任意取值的抽象的 Service 端口,其他 Pod 通过该端口访问 Service)。 查看 Service API 对象以了解 Service 所能接受的字段列表。 查看你的 Service 资源:

代码语言:javascript复制
kubectl get svc my-nginx 

NAME        TYPE    CLUSTER-IP  EXTERNAL-IP PORT(S) AGE 
my-nginx ClusterIP 10.0.162.149    <none>   80/TCP  21s

        正如前面所提到的,一个 Service 由一组 Pod 提供支撑。这些 Pod 通过 EndpointSlices 暴露出来。 Service Selector 将持续评估,结果被 POST 到使用标签与该 Service 连接的一个 EndpointSlice。 当 Pod 终止后,它会自动从包含该 Pod 的 EndpointSlices 中移除。 新的能够匹配上 Service Selector 的 Pod 将自动地被为该 Service 添加到 EndpointSlice 中。 检查 Endpoint,注意到 IP 地址与在第一步创建的 Pod 是相同的。

代码语言:javascript复制
kubectl describe svc my-nginx 

Name: my-nginx 
Namespace: default 
Labels: run=my-nginx 
Annotations: <none> 
Selector: run=my-nginx 
Type: ClusterIP 
IP: 10.0.162.149 
Port: <unset> 80/TCP 
Endpoints: 10.244.2.5:80,10.244.3.4:80 
Session Affinity: None 
Events: <none>

kubectl get endpointslices -l kubernetes.io/service-name=my-nginx 

NAME           ADDRESSTYPE PORTS       ENDPOINTS       AGE 
my-nginx-7vzhx     IPv4      80  10.244.2.5,10.244.3.4 21s

        现在,你应该能够从集群中任意节点上使用 curl 命令向 : 发送请求以访问 Nginx Service。 注意 Service IP 完全是虚拟的,它从来没有走过网络,如果对它如何工作的原理感到好奇, 可以进一步阅读服务代理的内容。

1.4 访问 Service

        Kubernetes支持两种查找服务的主要模式: 环境变量和 DNS。前者开箱即用,而后者则需要 CoreDNS 集群插件。

说明: 如果不需要服务环境变量(因为可能与预期的程序冲突,可能要处理的变量太多,或者仅使用DNS等),则可以通过在 pod spec 上将 enableServiceLinks 标志设置为 false 来禁用此模式。

1.4.1 环境变量

        当 Pod 在节点上运行时,kubelet 会针对每个活跃的 Service 为 Pod 添加一组环境变量。 这就引入了一个顺序的问题。为解释这个问题,让我们先检查正在运行的 Nginx Pod 的环境变量(你的环境中的 Pod 名称将会与下面示例命令中的不同):

代码语言:javascript复制
kubectl exec my-nginx-3800858182-jr4a2 -- printenv | grep SERVICE 

KUBERNETES_SERVICE_HOST=10.0.0.1 
KUBERNETES_SERVICE_PORT=443 
KUBERNETES_SERVICE_PORT_HTTPS=443

        能看到环境变量中并没有你创建的 Service 相关的值。这是因为副本的创建先于 Service。 这样做的另一个缺点是,调度器可能会将所有 Pod 部署到同一台机器上,如果该机器宕机则整个 Service 都会离线。 要改正的话,我们可以先终止这 2 个 Pod,然后等待 Deployment 去重新创建它们。 这次 Service 会 先于 副本存在。这将实现调度器级别的 Pod 按 Service 分布(假定所有的节点都具有同样的容量),并提供正确的环境变量:

代码语言:javascript复制
kubectl scale deployment my-nginx --replicas=0; kubectl scale deployment my-nginx --replicas=2; 

kubectl get pods -l run=my-nginx -o wide 

NAME                      READY  STATUS RESTARTS AGE     IP             NODE 
my-nginx-3800858182-e9ihh  1/1  Running    0     5s  10.244.2.7 kubernetes-minion-ljyd 
my-nginx-3800858182-j4rm4  1/1  Running    0     5s  10.244.3.8 kubernetes-minion-905m

        你可能注意到,Pod 具有不同的名称,这是因为它们是被重新创建的。

代码语言:javascript复制
kubectl exec my-nginx-3800858182-e9ihh -- printenv | grep SERVICE 

KUBERNETES_SERVICE_PORT=443 
MY_NGINX_SERVICE_HOST=10.0.162.149 
KUBERNETES_SERVICE_HOST=10.0.0.1 
MY_NGINX_SERVICE_PORT=80 
KUBERNETES_SERVICE_PORT_HTTPS=443

1.4.2 DNS

        Kubernetes 提供了一个自动为其它 Service 分配 DNS 名字的 DNS 插件 Service。 你可以通过如下命令检查它是否在工作:

代码语言:javascript复制
kubectl get services kube-dns --namespace=kube-system 

NAME       TYPE    CLUSTER-IP EXTERNAL-IP    PORT(S)    AGE 
kube-dns ClusterIP 10.0.0.10  <none>      53/UDP,53/TCP 8m

        本段剩余的内容假设你已经有一个拥有持久 IP 地址的 Service(my-nginx),以及一个为其 IP 分配名称的 DNS 服务器。 这里我们使用 CoreDNS 集群插件(应用名为 kube-dns), 所以在集群中的任何 Pod 中,你都可以使用标准方法(例如:gethostbyname())与该 Service 通信。 如果 CoreDNS 没有在运行,你可以参照 CoreDNS README 或者安装 CoreDNS 来启用它。 让我们运行另一个 curl 应用来进行测试:

代码语言:javascript复制
kubectl run curl --image=radial/busyboxplus:curl -i --tty 

Waiting for pod default/curl-131556218-9fnch to be running, status is Pending, pod ready: false Hit enter for command prompt

        然后,按回车并执行命令 nslookup my-nginx:

代码语言:javascript复制
[ root@curl-131556218-9fnch:/ ]$ nslookup my-nginx 

Server:    10.0.0.10 
Address 1: 10.0.0.10 

Name:      my-nginx 
Address 1: 10.0.162.149

1.5 保护 Service

        到现在为止,我们只在集群内部访问了 Nginx 服务器。在将 Service 暴露到因特网之前,我们希望确保通信信道是安全的。 为实现这一目的,需要:

  • 用于 HTTPS 的自签名证书(除非已经有了一个身份证书)
  • 使用证书配置的 Nginx 服务器
  • 使 Pod 可以访问证书的 Secret

        你可以从 Nginx https 示例获取所有上述内容。 你需要安装 go 和 make 工具。如果你不想安装这些软件,可以按照后文所述的手动执行步骤执行操作。简要过程如下:

代码语言:javascript复制
make keys KEY=/tmp/nginx.key CERT=/tmp/nginx.crt 
kubectl create secret tls nginxsecret --key /tmp/nginx.key --cert /tmp/nginx.crt 
secret/nginxsecret created

kubectl get secrets 

NAME              TYPE        DATA AGE 
nginxsecret kubernetes.io/tls  2   1m

        以下是 configmap:

代码语言:javascript复制
kubectl create configmap nginxconfigmap --from-file=default.conf configmap/nginxconfigmap created

kubectl get configmaps 

NAME           DATA AGE 
nginxconfigmap  1   114s

        以下是你在运行 make 时遇到问题时要遵循的手动步骤(例如,在 Windows 上):

代码语言:javascript复制
# 创建公钥和相对应的私钥 
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /d/tmp/nginx.key -out /d/tmp/nginx.crt -subj "/CN=my-nginx/O=my-nginx" 

# 对密钥实施 base64 编码 
cat /d/tmp/nginx.crt | base64 
cat /d/tmp/nginx.key | base64

        使用前面命令的输出来创建 yaml 文件,如下所示。 base64 编码的值应全部放在一行上。

代码语言:javascript复制
apiVersion: "v1" 
kind: "Secret" 
metadata: 
  name: "nginxsecret" 
  namespace: "default" 
type: kubernetes.io/tls 
data: 
  tls.crt: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURIekNDQWdlZ0F3SUJBZ0lKQUp5M3lQK0pzMlpJTUEwR0NTcUdTSWIzRFFFQkJRVUFNQ1l4RVRBUEJnTlYKQkFNVENHNW5hVzU0YzNaak1SRXdEd1lEVlFRS0V3aHVaMmx1ZUhOMll6QWVGdzB4TnpFd01qWXdOekEzTVRKYQpGdzB4T0RFd01qWXdOekEzTVRKYU1DWXhFVEFQQmdOVkJBTVRDRzVuYVc1NGMzWmpNUkV3RHdZRFZRUUtFd2h1CloybHVlSE4yWXpDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBSjFxSU1SOVdWM0IKMlZIQlRMRmtobDRONXljMEJxYUhIQktMSnJMcy8vdzZhU3hRS29GbHlJSU94NGUrMlN5ajBFcndCLzlYTnBwbQppeW1CL3JkRldkOXg5UWhBQUxCZkVaTmNiV3NsTVFVcnhBZW50VWt1dk1vLzgvMHRpbGhjc3paenJEYVJ4NEo5Ci82UVRtVVI3a0ZTWUpOWTVQZkR3cGc3dlVvaDZmZ1Voam92VG42eHNVR0M2QURVODBpNXFlZWhNeVI1N2lmU2YKNHZpaXdIY3hnL3lZR1JBRS9mRTRqakxCdmdONjc2SU90S01rZXV3R0ljNDFhd05tNnNTSzRqYUNGeGpYSnZaZQp2by9kTlEybHhHWCtKT2l3SEhXbXNhdGp4WTRaNVk3R1ZoK0QrWnYvcW1mMFgvbVY0Rmo1NzV3ajFMWVBocWtsCmdhSXZYRyt4U1FVQ0F3RUFBYU5RTUU0d0hRWURWUjBPQkJZRUZPNG9OWkI3YXc1OUlsYkROMzhIYkduYnhFVjcKTUI4R0ExVWRJd1FZTUJhQUZPNG9OWkI3YXc1OUlsYkROMzhIYkduYnhFVjdNQXdHQTFVZEV3UUZNQU1CQWY4dwpEUVlKS29aSWh2Y05BUUVGQlFBRGdnRUJBRVhTMW9FU0lFaXdyMDhWcVA0K2NwTHI3TW5FMTducDBvMm14alFvCjRGb0RvRjdRZnZqeE04Tzd2TjB0clcxb2pGSW0vWDE4ZnZaL3k4ZzVaWG40Vm8zc3hKVmRBcStNZC9jTStzUGEKNmJjTkNUekZqeFpUV0UrKzE5NS9zb2dmOUZ3VDVDK3U2Q3B5N0M3MTZvUXRUakViV05VdEt4cXI0Nk1OZWNCMApwRFhWZmdWQTRadkR4NFo3S2RiZDY5eXM3OVFHYmg5ZW1PZ05NZFlsSUswSGt0ejF5WU4vbVpmK3FqTkJqbWZjCkNnMnlwbGQ0Wi8rUUNQZjl3SkoybFIrY2FnT0R4elBWcGxNSEcybzgvTHFDdnh6elZPUDUxeXdLZEtxaUMwSVEKQ0I5T2wwWW5scE9UNEh1b2hSUzBPOStlMm9KdFZsNUIyczRpbDlhZ3RTVXFxUlU9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K" 
  tls.key: "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQ2RhaURFZlZsZHdkbFIKd1V5eFpJWmVEZWNuTkFhbWh4d1NpeWF5N1AvOE9ta3NVQ3FCWmNpQ0RzZUh2dGtzbzlCSzhBZi9WemFhWm9zcApnZjYzUlZuZmNmVUlRQUN3WHhHVFhHMXJKVEVGSzhRSHA3VkpMcnpLUC9QOUxZcFlYTE0yYzZ3MmtjZUNmZitrCkU1bEVlNUJVbUNUV09UM3c4S1lPNzFLSWVuNEZJWTZMMDUrc2JGQmd1Z0ExUE5JdWFubm9UTWtlZTRuMG4rTDQKb3NCM01ZUDhtQmtRQlAzeE9JNHl3YjREZXUraURyU2pKSHJzQmlIT05Xc0RadXJFaXVJMmdoY1kxeWIyWHI2UAozVFVOcGNSbC9pVG9zQngxcHJHclk4V09HZVdPeGxZZmcvbWIvNnBuOUYvNWxlQlkrZStjSTlTMkQ0YXBKWUdpCkwxeHZzVWtGQWdNQkFBRUNnZ0VBZFhCK0xkbk8ySElOTGo5bWRsb25IUGlHWWVzZ294RGQwci9hQ1Zkank4dlEKTjIwL3FQWkUxek1yall6Ry9kVGhTMmMwc0QxaTBXSjdwR1lGb0xtdXlWTjltY0FXUTM5SjM0VHZaU2FFSWZWNgo5TE1jUHhNTmFsNjRLMFRVbUFQZytGam9QSFlhUUxLOERLOUtnNXNrSE5pOWNzMlY5ckd6VWlVZWtBL0RBUlBTClI3L2ZjUFBacDRuRWVBZmI3WTk1R1llb1p5V21SU3VKdlNyblBESGtUdW1vVlVWdkxMRHRzaG9reUxiTWVtN3oKMmJzVmpwSW1GTHJqbGtmQXlpNHg0WjJrV3YyMFRrdWtsZU1jaVlMbjk4QWxiRi9DSmRLM3QraTRoMTVlR2ZQegpoTnh3bk9QdlVTaDR2Q0o3c2Q5TmtEUGJvS2JneVVHOXBYamZhRGR2UVFLQmdRRFFLM01nUkhkQ1pKNVFqZWFKClFGdXF4cHdnNzhZTjQyL1NwenlUYmtGcVFoQWtyczJxWGx1MDZBRzhrZzIzQkswaHkzaE9zSGgxcXRVK3NHZVAKOWRERHBsUWV0ODZsY2FlR3hoc0V0L1R6cEdtNGFKSm5oNzVVaTVGZk9QTDhPTm1FZ3MxMVRhUldhNzZxelRyMgphRlpjQ2pWV1g0YnRSTHVwSkgrMjZnY0FhUUtCZ1FEQmxVSUUzTnNVOFBBZEYvL25sQVB5VWs1T3lDdWc3dmVyClUycXlrdXFzYnBkSi9hODViT1JhM05IVmpVM25uRGpHVHBWaE9JeXg5TEFrc2RwZEFjVmxvcG9HODhXYk9lMTAKMUdqbnkySmdDK3JVWUZiRGtpUGx1K09IYnRnOXFYcGJMSHBzUVpsMGhucDBYSFNYVm9CMUliQndnMGEyOFVadApCbFBtWmc2d1BRS0JnRHVIUVV2SDZHYTNDVUsxNFdmOFhIcFFnMU16M2VvWTBPQm5iSDRvZUZKZmcraEppSXlnCm9RN3hqWldVR3BIc3AyblRtcHErQWlSNzdyRVhsdlhtOElVU2FsbkNiRGlKY01Pc29RdFBZNS9NczJMRm5LQTQKaENmL0pWb2FtZm1nZEN0ZGtFMXNINE9MR2lJVHdEbTRpb0dWZGIwMllnbzFyb2htNUpLMUI3MkpBb0dBUW01UQpHNDhXOTVhL0w1eSt5dCsyZ3YvUHM2VnBvMjZlTzRNQ3lJazJVem9ZWE9IYnNkODJkaC8xT2sybGdHZlI2K3VuCnc1YytZUXRSTHlhQmd3MUtpbGhFZDBKTWU3cGpUSVpnQWJ0LzVPbnlDak9OVXN2aDJjS2lrQ1Z2dTZsZlBjNkQKckliT2ZIaHhxV0RZK2Q1TGN1YSt2NzJ0RkxhenJsSlBsRzlOZHhrQ2dZRUF5elIzT3UyMDNRVVV6bUlCRkwzZAp4Wm5XZ0JLSEo3TnNxcGFWb2RjL0d5aGVycjFDZzE2MmJaSjJDV2RsZkI0VEdtUjZZdmxTZEFOOFRwUWhFbUtKCnFBLzVzdHdxNWd0WGVLOVJmMWxXK29xNThRNTBxMmk1NVdUTThoSDZhTjlaMTltZ0FGdE5VdGNqQUx2dFYxdEYKWSs4WFJkSHJaRnBIWll2NWkwVW1VbGc9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K"

        现在使用文件创建 Secret:

代码语言:javascript复制
kubectl apply -f nginxsecrets.yaml kubectl get secrets 

NAME             TYPE         DATA AGE 
nginxsecret kubernetes.io/tls  2   1m

        现在修改 nginx 副本以启动一个使用 Secret 中的证书的 HTTPS 服务器以及相应的用于暴露其端口(80 和 443)的 Service:service/networking/nginx-secure-app.yaml

代码语言:javascript复制
apiVersion: v1 
kind: Service 
metadata: 
  name: my-nginx 
  labels: 
    run: my-nginx 
spec: 
  type: NodePort 
  ports: 
    - port: 8080 
      targetPort: 80 
      protocol: TCP 
      name: http 
    - port: 443 
      protocol: TCP 
      name: https 
  selector: 
    run: my-nginx 

--- 
apiVersion: apps/v1 
kind: Deployment 
metadata: 
  name: my-nginx 
spec: 
  selector: 
    matchLabels: 
      run: my-nginx 
  replicas: 1 
  template: 
    metadata: 
      labels: 
        run: my-nginx 
    spec: 
      volumes: 
        - name: secret-volume 
          secret: 
            secretName: nginxsecret 
        - name: configmap-volume 
          configMap: 
            name: nginxconfigmap 
      containers: 
        - name: nginxhttps 
          image: bprashanth/nginxhttps:1.0 
          ports: 
            - containerPort: 443 
            - containerPort: 80 
          volumeMounts: 
            - mountPath: /etc/nginx/ssl 
              name: secret-volume 
            - mountPath: /etc/nginx/conf.d 
              name: configmap-volume

关于 nginx-secure-app 清单,值得注意的几点如下:

  • 它将 Deployment 和 Service 的规约放在了同一个文件中。
  • Nginx 服务器通过 80 端口处理 HTTP 流量,通过 443 端口处理 HTTPS 流量,而 Nginx Service 则暴露了这两个端口。
  • 每个容器能通过挂载在 /etc/nginx/ssl 的卷访问秘钥。卷和密钥需要在 Nginx 服务器启动 之前 配置好。
代码语言:javascript复制
kubectl delete deployments,svc my-nginx; kubectl create -f ./nginx-secure-app.yaml

        这时,你可以从任何节点访问到 Nginx 服务器。

代码语言:javascript复制
kubectl get pods -l run=my-nginx -o custom-columns=POD_IP:.status.podIPs 
    POD_IP 
    [map[ip:10.244.3.5]]

node $ curl -k https://10.244.3.5 
... 
<h1>Welcome to nginx!</h1>

        注意最后一步我们是如何提供 -k 参数执行 curl 命令的,这是因为在证书生成时, 我们不知道任何关于运行 nginx 的 Pod 的信息,所以不得不在执行 curl 命令时忽略 CName 不匹配的情况。 通过创建 Service,我们连接了在证书中的 CName 与在 Service 查询时被 Pod 使用的实际 DNS 名字。 让我们从一个 Pod 来测试(为了方便,这里使用同一个 Secret,Pod 仅需要使用 nginx.crt 去访问 Service):service/networking/curlpod.yaml

代码语言:javascript复制
apiVersion: apps/v1 
kind: Deployment 
metadata: 
  name: curl-deployment 
spec: 
  selector: 
    matchLabels: 
      app: curlpod 
  replicas: 1 
  template: 
    metadata: 
      labels: 
        app: curlpod 
    spec: 
      volumes: 
        - name: secret-volume 
          secret: 
            secretName: nginxsecret 
      containers: 
        - name: curlpod 
          command: 
            - sh 
            - -c 
            - while true; do sleep 1; done 
          image: radial/busyboxplus:curl 
          volumeMounts: 
            - mountPath: /etc/nginx/ssl 
              name: secret-volume
代码语言:javascript复制
kubectl apply -f ./curlpod.yaml kubectl get pods -l app=curlpod 

NAME                             READY  STATUS RESTARTS AGE 
curl-deployment-1515033274-1410r  1/1  Running    0     1m
代码语言:javascript复制
kubectl exec curl-deployment-1515033274-1410r -- curl https://my-nginx --cacert /etc/nginx/ssl/tls.crt 
... 
<title>Welcome to nginx!</title> 
...

1.6 暴露 Service

        对应用的某些部分,你可能希望将 Service 暴露在一个外部 IP 地址上。 Kubernetes 支持两种实现方式:NodePort 和 LoadBalancer。 在上一段创建的 Service 使用了 NodePort,因此,如果你的节点有一个公网 IP,那么 Nginx HTTPS 副本已经能够处理因特网上的流量。

代码语言:javascript复制
kubectl get svc my-nginx -o yaml | grep nodePort -C 5
代码语言:javascript复制
  uid: 07191fb3-f61a-11e5-8ae5-42010af00002 
spec: 
  clusterIP: 10.0.162.149 
  ports: 
    - name: http 
      nodePort: 31704 
      port: 8080 
      protocol: TCP 
      targetPort: 80 
    - name: https 
      nodePort: 32453 
      port: 443 
      protocol: TCP 
      targetPort: 443 
  selector: 
    run: my-nginx
代码语言:javascript复制
kubectl get nodes -o yaml | grep ExternalIP -C 1
代码语言:javascript复制
      - address: 104.197.41.11 
        type: ExternalIP 
      allocatable: 
-- 
      - address: 23.251.152.56 
        type: ExternalIP 
      allocatable: 
... 

$ curl https://<EXTERNAL-IP>:<NODE-PORT> -k 
... 
<h1>Welcome to nginx!</h1>

        让我们重新创建一个 Service 以使用云负载均衡器。 将 my-nginx Service 的 Type 由 NodePort 改成 LoadBalancer:

代码语言:javascript复制
kubectl edit svc my-nginx 
kubectl get svc my-nginx

NAME          TYPE     CLUSTER-IP    EXTERNAL-IP     PORT(S)     AGE 
my-nginx LoadBalancer 10.0.162.149 xx.xxx.xxx.xxx 8080:30163/TCP 21s
代码语言:javascript复制
curl https://<EXTERNAL-IP> -k 

... 
<title>Welcome to nginx!</title>

        在 EXTERNAL-IP 列中的 IP 地址能在公网上被访问到。CLUSTER-IP 只能从集群/私有云网络中访问。

        注意,在 AWS 上,类型 LoadBalancer 的服务会创建一个 ELB,且 ELB 使用主机名(比较长),而不是 IP。 ELB 的主机名太长以至于不能适配标准 kubectl get svc 的输出,所以需要通过执行 kubectl describe service my-nginx 命令来查看它。 可以看到类似如下内容:

代码语言:javascript复制
kubectl describe service my-nginx 
... 
LoadBalancer Ingress: a320587ffd19711e5a37606cf4a74574-1142138393.us-east-1.elb.amazonaws.com 
...

参考链接

使用 Service 连接到应用 | Kubernetes

详解k8s的4种Service类型_Dark_Ice_的博客-CSDN博客_k8s service类型

k8s之Service_江南道人的博客-CSDN博客_k8s查看service

k8s重器之Service

k8s 理解Service工作原理 - 知乎

k8s之service服务 - 李志锋 - 博客园

k8s的Service详解

K8S之Service详解_运维@小兵的博客-CSDN博客_k8sservice

k8s之Service详解-Service介绍 - 路过的柚子厨 - 博客园

一文讲透K8s的Service概念

服务(Service) | Kubernetes

Service · Kubernetes指南

0 人点赞