【Kubernetes 系列】一文学会Kubernetes Service安全的暴露应用

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

作者半身风雪
上一节K8S 中Pod 的生命周期
简介上一节我们一起学习了Kubernetes 应用Pod 生命周期的三种状态 Pending -> Running -> Succeeded/Failed。本节内容我们将一起学习怎么暴露我们的应用。


Service安全的暴露应用


目标

  • 学习 Kubernetes 中的 Service
  • 理解 标签(Label) 和 标签选择器(Label Selector) 对象如何与 Service 关联
  • 使用Service 连接到应用
  • 在 Kubernetes 集群外用 Service 暴露应用

一、什么是Kubernetes Service

在上一节中我们学习到了pod 的生命周期当一个工作 Node 挂掉后, 在 Node 上运行的 Pod 也会消亡。 ReplicaSet 会自动地通过创建新的 Pod 驱动集群回到目标状态以保证应用程序正常运行。

Kubernetes 中的服务(Service)是一种抽象概念它定义了 Pod 的逻辑集和访问 Pod 的协议。Service 使从属 Pod 之间的松耦合成为可能。 和其他 Kubernetes 对象一样, Service 用 YAML (更推荐) 或者 JSON 来定义 Service 下的一组 Pod 通常由 LabelSelector 来标记。

尽管每个 Pod 都有一个唯一的 IP 地址但是如果没有 Service 这些 IP 不会暴露在集群外部。Service 允许你的应用程序接收流量。Service 也可以用在 ServiceSpec 标记type的方式暴露

  • ClusterIP (默认) - 在集群的内部 IP 上公开 Service 。这种类型使得 Service 只能从集群内访问。
  • NodePort - 使用 NAT 在集群中每个选定 Node 的相同端口上公开 Service 。使用<NodeIP>:<NodePort> 从集群外部访问 Service。是 ClusterIP 的超集。
  • LoadBalancer - 在当前云中创建一个外部负载均衡器(如果支持的话)并为 Service 分配一个固定的外部IP。是 NodePort 的超集。
  • ExternalName - 通过返回带有该名称的 CNAME 记录使用任意名称(由 spec 中的externalName指定)公开 Service。不使用代理。这种类型需要kube-dns的v1.7或更高版本。

二、使用 Service 连接到应用

Kubernetes 假设 Pod 可与其它 Pod 通信不管它们在哪个主机上。 Kubernetes 给每一个 Pod 分配一个集群私有 IP 地址所以没必要在 Pod 与 Pod 之间创建连接或将容器的端口映射到主机端口。 这意味着同一个 Pod 内的所有容器能通过 localhost 上的端口互相连通集群中的所有 Pod 也不需要通过 NAT 转换就能够互相看到。

2.1、在集群中暴露 Pod

首先我们先启动一个项目具体的启动方式我们前面都讲过了在这里就不多做赘述。

  1. 项目运行之后打开Kubernetes 仪表板Dashboard点击右上角的号使用YAML 的方式创建一个pod 节点。

在这里插入图片描述

  1. 如上图所示在YAML 中添加如下代码
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
  1. 点击上传再次点击左侧列表栏的pod 选项就回到了我们之前 所熟悉的pod 页面。

在这里插入图片描述

当然我们也可以直接使用命令行的方式来检查我们的节点

$ kubectl apply -f ./run-my-nginx.yaml

如果报以下错误的话:

在这里插入图片描述

直接执行命令

$ unset KUBECONFIG

再次执行

$ kubectl apply -f ./run-my-nginx.yaml
$ kubectl get pods -l run=my-nginx -o wide

执行结果输出节点如下

在这里插入图片描述

当然我们也可以使用命令行检查pod 的IP地址

$ kubectl get pods -l run=my-nginx -o yaml | grep podIP

输出当前节点IP

在这里插入图片描述

我们能够通过 ssh 登录到集群中的任何一个节点上并使用诸如 curl 之类的工具向这两个 IP 地址发出查询请求。 需要注意的是容器不会使用该节点上的 80 端口也不会使用任何特定的 NAT 规则去路由流量到 Pod 上。 这意味着可以在同一个节点上运行多个 Nginx Pod使用相同的 containerPort并且可以从集群中任何其他的 Pod 或节点上使用 IP 的方式访问到它们。

2.2、创建 Service

上面创建了一个扁平的、集群范围的地址空间中运行 Nginx 服务的 Pod 要想把它暴露出去我们还需要创建一个Service。

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

直接使用 命令给 上面的 Nginx 副本创建一个 Service

$ kubectl expose deployment/my-nginx
$ service/my-nginx exposed

当然我们也可以直接使用YAML 方式去创建创建方式和上面创建pod 的一样可以直接使用如下代码

apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
  selector:
    run: my-nginx

此时点击左侧菜单栏中的Serverces你会看到当前Serverces 的详细信息

在这里插入图片描述

上面我们创建的 Service 会将所有具有标签 run: my-nginx 的 Pod 的 TCP 80 端口暴露到一个抽象的 Service 端口上targetPort容器接收流量的端口port可任意取值的抽象的 Service 端口其他 Pod 通过该端口访问 Service。

现在我们执行下面的命令查看Service 资源:

$ kubectl get svc my-nginx

资源输出如下图所示

在这里插入图片描述

前面我也提到一个 Service 由一组 Pod 提供支撑。这些 Pod 通过 endpoints 暴露出来。 Service Selector 将持续评估结果被 POST 到一个名称为 my-nginxEndpoint 对象上。 当 Pod 终止后它会自动从 Endpoint 中移除新的能够匹配上 Service Selector 的 Pod 将自动地被添加到 Endpoint 中。 检查该 Endpoint注意到 IP 地址与在第一步创建的 Pod 是相同的。

$ kubectl describe svc my-nginx

执行上面的命令我们可以得到当前serverces 的所有信息

在这里插入图片描述

$ kubectl get ep my-nginx

现在我们执行上面的命令就可以暴露我们的service了执行结果如下

NAME ENDPOINTS AGE
my-nginx 172.17.0.3:80,172.17.0.8:80 23m

现在我们就能够从集群中任意节点上使用 curl 命令向 <CLUSTER-IP>:<PORT> 发送请求以访问 Nginx Service。

2.3、访问 Service

Kubernetes支持两种查找服务的主要模式: 环境变量和 DNS。这里我将只介绍环境变量的查找方式。

当 Pod 在节点上运行时kubelet 会针对每个活跃的 Service 为 Pod 添加一组环境变量。 这就引入了一个顺序的问题。为解释这个问题让我们先检查正在运行的 Nginx Pod 的环境变量。

我个人的环境变量是 my-nginx-cf54cdbf7-vr96x 你的可能会和我的不一样直接在pod 中查看直接的环境变量。

在这里插入图片描述

接下来执行命令

$ kubectl exec my-nginx-cf54cdbf7-vr96x – printenv | grep SERVICE

运行结果如下

KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_PORT_HTTPS=443

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

分别执行如下两行命令

$ kubectl scale deployment my-nginx --replicas=0; kubectl scale deployment my-nginx --replicas=2;
$ kubectl get pods -l run=my-nginx -o wide

在这里插入图片描述

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

现在我们再次执行Nginx Pod 环境变量命令注意现在你的环境变量又变了请验证

$ kubectl exec my-nginx-cf54cdbf7-fhghk – printenv | grep SERVICE

终于我们在运行结果中看到了Service 相关的值

KUBERNETES_SERVICE_PORT_HTTPS=443
MY_NGINX_SERVICE_HOST=10.97.238.70
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_SERVICE_PORT=443
MY_NGINX_SERVICE_PORT=80

2.4、保护 Service

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

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

我们可以直接通过手动执行步骤执行操作下列命令:

$ 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
default-token-t7mbb kubernetes.io/service-account-token 3 56m

以下是 configmap

$ kubectl create configmap nginxconfigmap --from-file=default.conf
$ configmap/nginxconfigmap created
$ ubectl get configmaps

输出结果如下

NAME DATA AGE
kube-root-ca.crt 1 58m

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

# 创建公钥和相对应的私钥
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 编码的值应全部放在一行上。

apiVersion: "v1"
kind: "Secret"
metadata:
  name: "nginxsecret"
  namespace: "default"
type: kubernetes.io/tls  
data:
  tls.crt: "这里放你的编码"
  tls.key: "这里放你的编码"

现在使用文件创建 Secret

$ kubectl apply -f nginxsecrets.yaml
$ kubectl get secrets

执行结果

NAME TYPE DATA AGE
default-token-t7mbb kubernetes.io/service-account-token 3 62m

现在修改 nginx 副本以启动一个使用 Secret 中的证书的 HTTPS 服务器以及相应的用于暴露其端口80 和 443的 Service

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 服务器启动之前配置好。

$ kubectl delete deployments,svc my-nginx; kubectl create -f ./nginx-secure-app.yaml

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

kubectl get pods -o yaml | grep -i podip
    podIP: 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 来测试为了方便这里使用同一个 SecretPod 仅需要使用 nginx.crt 去访问 Service

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

再执行下面的命令

$ 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

kubectl exec curl-deployment-1515033274-1410r -- curl https://my-nginx --cacert /etc/nginx/ssl/tls.crt
...
<title>Welcome to nginx!</title>
...

2.5、暴露 Service

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

$ kubectl get svc my-nginx -o yaml | grep nodePort -C 5

在这里插入图片描述

$ kubectl get nodes -o yaml | grep ExternalIP -C 1

在这里插入图片描述

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

$ 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

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

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

在这里插入图片描述

需要注意的是有一些 Service 的用例没有在 spec 中定义selector。 一个没有selector创建的 Service 也不会创建相应的端点对象。这允许用户手动将服务映射到特定的端点。没有 selector 的另一种可能是你严格使用type: ExternalName来标记。

三、Service 和 Label

在这里插入图片描述

Service 通过一组 Pod 路由通信。Service 是一种抽象它允许 Pod 死亡并在 Kubernetes 中复制而不会影响应用程序。在依赖的 Pod (如应用程序中的前端和后端组件)之间进行发现和路由是由Kubernetes Service 处理的。

Service 匹配一组 Pod 是使用 标签(Label)和选择器(Selector), 它们是允许对 Kubernetes 中的对象进行逻辑操作的一种分组原语。标签(Label)是附加在对象上的键/值对可以以多种方式使用:

  • 指定用于开发测试和生产的对象
  • 嵌入版本标签
  • 使用 Label 将对象进行分类

在这里插入图片描述

标签(Label)可以在创建时或之后附加到对象上。他们可以随时被修改。

总结

本节内容主要讲解了 service 模块及它的关联标签还有 service 如何将集群暴露给外部内容还是比较多的创作不易望支持。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: k8s