如何在Kubernetes中暴露非HTTP的TCP服务?

20

我在公共云(Azure/AWS/Google Cloud)上运行一个Kubernetes集群,有一些非HTTP服务我想要向用户暴露。

对于HTTP服务,我通常会使用Ingress资源通过可寻址的DNS条目将该服务公开。

针对非HTTP、基于TCP协议的服务(例如PostgreSQL数据库),我应该如何向公众公开这些服务呢?

我考虑使用NodePort服务,但这需要节点本身是公开可访问的(依赖kube-proxy路由到适当的节点)。如果可能的话,我希望避免这种情况。

LoadBalancer服务似乎是另一个选择,但我不想为我想要暴露的每个TCP服务创建专用的云负载均衡器。

我知道NGINX Ingress控制器支持公开TCP和UDP服务,但那似乎需要静态定义你想要公开的服务。对于我的用例,这些服务是动态创建和销毁的,因此无法事先在静态ConfigMap中定义这些服务映射。


3个回答

1
几年后才回复,但这是我为类似情况所做的事情。如果您不想使用LoadBalancer,则另一个选项是NodePort,就像您提到的那样。为了使其具有外部可寻址性,当pod启动时,您可以使其节点关联静态IP。例如,在AWS EC2中,您可以拥有弹性IP(或GCP中的静态外部IP),并在postgresql pod在其PodSpec中启动时使用initContainers或单独的容器进行关联。
  initContainers:
  - name: eip
    image: docker.io/amazon/aws-cli:2.7.1
    command:
    - /bin/bash
    - -xec
    - |
      INSTANCE_ID="$(curl http://169.254.169.254/latest/meta-data/instance-id)"
      aws ec2 associate-address --allocation-id "$EIP_ALLOCATION_ID" --instance-id "$INSTANCE_ID"
    env:
    - name: EIP_ALLOCATION_ID
      value: <your elastic IP allocation ID>
    - name: AWS_REGION
      value: <region>
    - name: AWS_ACCESS_KEY_ID
      valueFrom:
        secretKeyRef:
          name: aws-secret
          key: accessKey
    - name: AWS_SECRET_ACCESS_KEY
      valueFrom:
        secretKeyRef:
          name: aws-secret
          key: secretKey

假设 AWS 访问密钥和秘密密钥已安装在“aws-secret”中。上面的示例使用环境变量,但为了更安全,您可以将其挂载到卷上,在脚本正文中读取并在使用后取消设置。
为缓解与端口相关的安全问题,一个选项是仅向安全组添加一个或两个节点,暴露该端口,并使用 nodeSelector 将 pod 固定在这些节点上。或者,您可以在上面的容器正文中使用 "aws ec2 modify-instance-attribute",将安全组添加到仅运行 pod 的节点。下面是一个更完整的 AWS EC2 示例,处理具有多个网络接口的节点:
  containers:
  - name: eip-sg
    image: docker.io/amazon/aws-cli:2.7.1
    command:
    - /bin/bash
    - -xec
    - |
      INSTANCE_ID=$(curl http://169.254.169.254/latest/meta-data/instance-id)
      PRIVATE_IP="$(curl http://169.254.169.254/latest/meta-data/local-ipv4)"
      for iface in $(curl http://169.254.169.254/latest/meta-data/network/interfaces/macs/); do
        if curl "http://169.254.169.254/latest/meta-data/network/interfaces/macs/$iface/local-ipv4s" | grep -q "$PRIVATE_IP"; then
          INTERFACE_FOR_IP="$(curl http://169.254.169.254/latest/meta-data/network/interfaces/macs/$iface/interface-id)"
        fi
      done
      if [ -z "$INTERFACE_FOR_IP" ]; then
        aws ec2 associate-address --allocation-id "$EIP_ALLOCATION_ID" --instance-id "$INSTANCE_ID"
      else
        aws ec2 associate-address --allocation-id "$EIP_ALLOCATION_ID" --network-interface-id "$INTERFACE_FOR_IP" --private-ip-address "$PRIVATE_IP"
      fi
      aws ec2 modify-instance-attribute --instance-id "$INSTANCE_ID" --groups $FULL_SECURITY_GROUPS
      tail -f -s 10 /dev/null
    lifecycle:
      preStop:
        exec:
          command:
          - /bin/bash
          - -ec
          - |
            INSTANCE_ID=$(curl http://169.254.169.254/latest/meta-data/instance-id)
            aws ec2 modify-instance-attribute --instance-id "$INSTANCE_ID" --groups $DEFAULT_SECURITY_GROUPS
    env:
    - name: EIP_ALLOCATION_ID
      value: <your elastic IP allocation ID>
    - name: DEFAULT_SECURITY_GROUPS
      value: "<sg1> <sg2>"
    - name: FULL_SECURITY_GROUPS
      value: "<sg1> <sg2> <sg3>"
    - name: AWS_REGION
      value: <region>
    - name: AWS_ACCESS_KEY_ID
      valueFrom:
        secretKeyRef:
          name: aws-secret
          key: accessKey
    - name: AWS_SECRET_ACCESS_KEY
      valueFrom:
        secretKeyRef:
          name: aws-secret
          key: secretKey

您可能还需要将部署策略设置为Recreate,而不是默认的RollingUpdate,否则在使用kubectl rollout restart deployment ...重新启动部署时,容器command之后可能会调用lifecycle.preStop


1

也许这个工作流可以帮助:

(我假设云提供商是AWS)

  • AWS 控制台:创建一个隔离的VPC,并创建您的Kubernetes ec2实例(或自动缩放组),禁用公共IP的创建。这使得无法通过Internet访问实例,但仍可以通过私有IP(例如,172.30.1.10)通过站点到站点VPN或通过同一VPC中具有公共IP的辅助ec2实例进行访问。

  • Kubernetes:创建一个具有固定节点端口的服务(例如,Postgres的35432)。

  • AWS 控制台:在您的节点所在的同一VPC内创建一个经典的Layer 4负载平衡器,在“侦听器”选项卡中打开端口35432(和其他可能需要的端口),通过“目标组”将其指向一个或多个节点。端口数量没有收费。

此时,我不知道如何自动更新负载均衡器目标组中的当前活动节点,这可能与自动扩展功能有关,如果有的话...也许可以使用一个Cron作业和一个从AWS API拉取信息并更新目标组的bash脚本来解决?

2
谢谢 - 这很有帮助。尽管显然这不是适用于任何Kubernetes环境的通用解决方案,而是与所选云提供商紧密相关。也许Operator是一个很好的选择,可以隐藏所选云提供商的细节? - cjheppell

1
对于非HTTP、基于TCP的服务(例如PostgreSQL数据库),我应该如何公开这些服务以供公众使用? 这取决于您希望最终用户如何寻址这些服务。正如您所指出的,使用Ingress,可以使用虚拟主机将所有请求路由到同一个Ingress控制器,然后使用“Host:”标头在集群内进行调度。 对于像PostgreSQL这样的TCP服务,没有这样的标头。因此,您必须具有基于IP的机制或为每个服务分配一个专用端口在面向Internet的IP上。 如果您的客户端支持IPv6,则给每个服务分配一个专用IP地址是完全合理的,因为IPv6提供了绝对巨大的IP空间。但否则,您有两个旋钮可供调整:IP和端口。 从那里开始,如何将这些连接路由到正确的服务取决于您如何解决第一个问题。

1
感谢您的回答。我知道每个服务都会暴露自己独特的端口。我的问题是,当前的Kubernetes服务类型似乎不适合我的用例。NodePort需要公开节点上的端口,这从安全角度来看并不理想。每个服务的LoadBalancer则很昂贵。假设我可以正确地路由到主机,那么在这些服务动态启动和关闭时,我应该使用哪种Kubernetes服务来使它们可以公开连接? - cjheppell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接