教程:适用于 pods 的安全组 - Amazon EKS
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅中国的 Amazon Web Services 服务入门

教程:适用于 pods 的安全组

适用于 pods 的安全组将 Amazon EC2 安全组与 Kubernetes pods 集成在一起。您可以使用 Amazon EC2 安全组定义允许流向和来自于您部署到运行在许多 Amazon EC2 实例类型和 Fargate 上节点的 pods 的入站和出站网络流量的规则。有关此功能的详细说明,请参阅 pods 的安全组介绍博客文章。

注意事项

在部署适用于 pods 的安全组之前,请考虑以下限制和条件:

  • 适用于 pods 的安全组不能与 Windows 节点一起使用。

  • pods 的安全组不能与为包含 Amazon EC2 节点的 IPv6 系列配置的集群一起使用。但是,您可以将 pods 的安全组与为仅包含 Fargate 节点的 IPv6 系列配置的集群一起使用。有关更多信息,请参阅教程:将 IPv6 地址分配给 pods 和 services

  • 大多数基于 Nitro 的 Amazon EC2 实例系列都支持 pods 的安全组,包括 m5c5r5p3m6gc6gr6g 实例系列。t3 实例系列不受支持。有关受支持的实例的完整集,请参阅 GitHub 上的 limits.go 文件。您的节点必须是在该文件中拥有 IsTrunkingCompatible: true 的所列出的实例类型之一。

  • 如果您还使用 pod 安全策略来限制对 pod 变异的访问,则 eks-vpc-resource-controllervpc-resource-controller Kubernetes 服务账户必须在 Kubernetes ClusterRoleBinding 中指定用于 psp 所分配到的 role。如果您使用的是默认 Amazon EKS psp、role 和 ClusterRoleBinding,则此为 eks:podsecuritypolicy:authenticated ClusterRoleBinding。例如,您将服务账户添加到 subjects: 部分,如以下示例所示:

    ... subjects: - kind: Group apiGroup: rbac.authorization.k8s.io name: system:authenticated - kind: ServiceAccount name: vpc-resource-controller - kind: ServiceAccount name: eks-vpc-resource-controller
  • 如果您将自定义联网和适用于 pods 的安全组一起使用,则请使用适用于 pods 的安全组所指定的安全组,不要使用 ENIconfig 中指定的安全组。

  • 如果您使用的是版本 1.10.2 或更早版本的 Amazon VPC CNI 插件,并且将 terminationGracePeriodSeconds 设置包括在您的 pod 规范中,则该设置的值不能为零。

  • 如果您使用的是版本 1.10 或更早版本的 Amazon VPC CNI 插件或 POD_SECURITY_GROUP_ENFORCING_MODE=strict 的版本 1.11(默认设置),则使用 externalTrafficPolicy 设置为 Local 的实例目标的 NodePortLoadBalancer 型 Kubernetes 服务在您将安全组分配到的 pods 中不受支持。有关将负载均衡器与实例目标一起使用的更多信息,请参阅 Amazon EKS 上的网络负载均衡。如果您使用的是 POD_SECURITY_GROUP_ENFORCING_MODE=standard 的版本 1.11 或者更高版本的插件,则支持 externalTrafficPolicy 被设置为 Local 的实例类型。

  • 如果您使用的是版本 1.10 或更早版本的 Amazon VPC CNI 插件或 POD_SECURITY_GROUP_ENFORCING_MODE=strict 的版本 1.11(默认设置),对于来自分配有安全组的 pods 的出站流量,禁用源 NAT,以便应用出站安全组规则。要访问 Internet,必须在配置了 NAT 网关或实例的私有子网中部署的节点上启动具有已分配安全组的 pods。将分配的安全组部署到公有子网的 Pods 无法访问 Internet。

    如果您使用的是 POD_SECURITY_GROUP_ENFORCING_MODE=standard 的版本 1.11 或者更高版本的插件,则发往 VPC 之外的 pod 流量将转换为实例主网络接口的 IP 地址。对于此流量,将使用主网络接口的安全组中的规则,而不是 pod's 安全组中的规则。

  • 要将 Calico 网络策略用于具有关联安全组的 pods,您必须使用版本 1.11.0 或更高版本的 Amazon VPC CNI 插件并设置 POD_SECURITY_GROUP_ENFORCING_MODE=standard。否则,流向和来自具有关联安全组的 pods 的流量不受 Calico 网络策略执行限制,并且仅受限于 Amazon EC2 安全组执行。要更新 Amazon VPC CNI 版本,请参阅 管理 Amazon VPC CNI plugin for Kubernetes

  • 在使用集群中的安全组的 Amazon EC2 节点上运行且使用 Nodelocal DNSCache 的 Pods 仅支持版本 1.11.0 或更高版本 Amazon VPC CNI 插件和 POD_SECURITY_GROUP_ENFORCING_MODE=standard。要更新 Amazon VPC CNI 插件版本,请参阅 管理 Amazon VPC CNI plugin for Kubernetes

  • 对于具有高流失率的 pods 而言,适用于容器组 (pod) 的安全组可能会导致更高的 pod 启动延迟。这是由于资源控制器中的速率限制造成的。

为适用于 pods 的安全组配置 Amazon VPC CNI plugin for Kubernetes

要部署适用于 pods 的安全组

如果您使用的是仅适用于 Fargate pods 的安全组,而且集群上没有任何 Amazon EC2 节点,请跳至 部署示例应用程序

  1. 使用以下命令查看您当前的 Amazon VPC CNI plugin for Kubernetes 版本:

    kubectl describe daemonset aws-node --namespace kube-system | grep amazon-k8s-cni: | cut -d : -f 3

    输出示例如下。

    v1.7.6

    如果您的 Amazon VPC CNI plugin for Kubernetes 版本低于 1.7.7,请将该插件更新到版本 1.7.7 或更高版本。有关更多信息,请参阅更新 Amazon VPC CNI plugin for Kubernetes 附加组件

  2. AmazonEKSVPCResourceController 托管 IAM policy 添加到与您的 Amazon EKS 集群关联的集群角色。策略允许角色管理网络接口、网络接口的私有 IP 地址以及与网络实例之间的连接和分离。

    1. 检索您的集群 IAM 角色的名称,然后将其存储在一个变量中。将 my-cluster 替换为您的集群名称。

      cluster_role=$(aws eks describe-cluster --name my-cluster --query cluster.roleArn --output text | cut -d / -f 2)
    2. 将 策略附加到该角色。

      aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/AmazonEKSVPCResourceController --role-name $cluster_role
  3. 通过在 aws-node DaemonSet 中将 ENABLE_POD_ENI 变量设置为 true,启用 Amazon VPC CNI 附加组件来管理 pods 的网络接口。此设置一旦设置为 true 后,附加组件会为集群中的每个节点添加一个带有值 vpc.amazonaws.com/has-trunk-attached=true 的标注。VPC 资源控制器会创建并附加一个带有 aws-k8s-trunk-eni 描述且名为中继网络接口的特殊网络接口。

    kubectl set env daemonset aws-node -n kube-system ENABLE_POD_ENI=true
    注意

    中继网络接口包含在实例类型支持的最大网络接口数中。有关每种实例类型支持的最大网络接口数的列表,请参阅《适用于 Linux 实例的 Amazon EC2 用户指南》中的每种实例类型的每个网络接口的 IP 地址数。如果您的节点已经附加了最大数量的标准网络接口,则 VPC 资源控制器将预订一个空间。您将必须缩减您正在运行的 pods,以便控制器足以分离和删除标准网络接口、创建中继网络接口并将其附加到实例。

    您可以使用以下命令查看哪些节点的 aws-k8s-trunk-eni 设置为 true。如果返回 No resources found,则等几秒钟后重试。上一步需要重新启动 Amazon VPC CNI plugin for Kubernetes pods,这需要几秒钟的时间。

    kubectl get nodes -o wide -l vpc.amazonaws.com/has-trunk-attached=true

    创建中继网络接口后,可以从中继或标准网络接口为 pods 分配辅助 IP 地址。如果节点被删除,中继接口将自动删除。

    当您在后面的步骤中部署适用于 pod 的安全组时,VPC 资源控制器会创建一个具有 aws-k8s-branch-eni 描述的名为分支网络接口的特殊网络接口,并将安全组与其关联。除了附加到节点的标准网络接口和中继网络接口之外,还会创建分支网络接口。如果您使用的是存活探测或就绪探测器,则您还需要禁用 TCP 早期解复用器,以便 kubelet 可以使用 TCP 连接到分支网络接口上的 pods。要禁用 TCP 早期解复用器,请运行以下命令:

    kubectl patch daemonset aws-node -n kube-system \ -p '{"spec": {"template": {"spec": {"initContainers": [{"env":[{"name":"DISABLE_TCP_EARLY_DEMUX","value":"true"}],"name":"aws-vpc-cni-init"}]}}}}'
    注意

    如果您使用的是 1.11.0 或更高版本的 Amazon VPC CNI plugin for Kubernetes 附加组件并设置 POD_SECURITY_GROUP_ENFORCING_MODE=standard,如下一步所述,则您就不需要运行上一个命令。

  4. 如果您的集群使用 NodeLocal DNSCache,或者您想将 Calico 网络策略与拥有自己的安全组的 pods 配合使用,或者您有 NodePortLoadBalancer 类型的 Kubernetes 服务,将 externalTrafficPolicy 设置为 Local 的实例目标用于您要将安全组分配到的 pods,则您必须使用版本 1.11.0 或更高版本的 Amazon VPC CNI plugin for Kubernetes 附加组件,并且必须启用以下设置:

    kubectl set env daemonset aws-node -n kube-system POD_SECURITY_GROUP_ENFORCING_MODE=standard
    重要
    • Pod 安全组规则不适用于位于相同节点上的 pods 之间或介于 pods 和services之间之外的流量,例如 kubeletnodeLocalDNS

    • 从 pods 到 VPC 以外的地址的出站流量是转换为实例的主网络接口的 IP 地址的网络地址(除非您还设置了 AWS_VPC_K8S_CNI_EXTERNALSNAT=true)。对于此流量,将使用主网络接口的安全组中的规则,而不是 pod's 安全组中的规则。

    • 要将此设置应用于现有 pods,必须重新启动 pods 在其上运行的 pods 或节点。

部署示例应用程序

要将安全组用于 pods,您必须拥有现有安全组并且部署 Amazon EKS SecurityGroupPolicy 到集群中,如以下程序所述。以下步骤介绍如何将安全组策略用于 pod。除非另有说明,请从同一个终端完成所有步骤,因为以下步骤中使用的变量不会在终端之间持续存在。

要使用安全组部署示例 pod

  1. 创建要与 pod 一起使用的安全组。后续步骤可帮助您创建一个简单的安全组,仅用于说明目的。在生产集群中,您的规则可能会有所不同。

    1. 使用您的集群的名称创建变量。将 my-cluster 替换为您的集群名称。

      my_cluster_name=my-cluster
    2. 检索您的集群的 VPC 和集群安全组的 ID,然后将它们存储在变量中。

      my_cluster_vpc_id=$(aws eks describe-cluster --name $my_cluster_name --query cluster.resourcesVpcConfig.vpcId --output text) my_cluster_security_group_id=$(aws eks describe-cluster --name $my_cluster_name --query cluster.resourcesVpcConfig.clusterSecurityGroupId --output text)
    3. 使用您的安全组的名称设置变量。将 my-pod-security-group 替换为您自己的名称。

      my_pod_security_group_name=my-pod-security-group
    4. 为您的 pod 创建安全组。

      aws ec2 create-security-group --vpc-id $my_cluster_vpc_id --group-name $my_pod_security_group_name --description "My pod security group" my_pod_security_group_id=$(aws ec2 describe-security-groups --filters Name=group-name,Values=$my_pod_security_group_name --query 'SecurityGroups[].GroupId' --output text)
    5. 允许 TCP 和 UDP 端口 53 流量从您在上一步中创建的 pod 安全组发送到您的集群安全组。如果您希望 DNS 流量从您的 pod 流向与集群安全组不同的安全组,则将 $my_cluster_security_group_id 替换为您自己的安全组 ID。记下每个命令返回的输出中的 SecurityGroupRuleId 返回的值。您将在后面的步骤中用到它们。

      my_tcp_rule_id=$(aws ec2 authorize-security-group-ingress --group-id $my_cluster_security_group_id \ --protocol tcp --port 53 --source-group $my_pod_security_group_id --query SecurityGroupRules[].SecurityGroupRuleId --output text) my_udp_rule_id=$(aws ec2 authorize-security-group-ingress --group-id $my_cluster_security_group_id \ --protocol udp --port 53 --source-group $my_pod_security_group_id --query SecurityGroupRules[].SecurityGroupRuleId --output text)
    6. 允许通过任何协议和端口从与安全组关联的任何 pod 到您的 pod 安全组的入站流量。安全组具有默认出站规则,允许通过任何协议和端口从与安全组关联的 pods 到您的任何目的地的出站流量。

      my_inbound_self_rule_id=$(aws ec2 authorize-security-group-ingress --group-id $my_pod_security_group_id \ --protocol -1 --port -1 --source-group $my_pod_security_group_id --query SecurityGroupRules[].SecurityGroupRuleId --output text)
  2. 创建要将资源部署到该处的 Kubernetes 命名空间。

    kubectl create namespace my-namespace
  3. 将 Amazon EKS SecurityGroupPolicy 部署到您的集群。

    1. 将以下内容复制到您的设备。如果您宁愿根据服务账户标签选择 pods,则可以将 podSelector 替换为 serviceAccountSelector。您必须指定一个或其他选择器。空的 podSelector(示例:podSelector: {})会选择命名空间中的所有 pods。空的 serviceAccountSelector 会选择命名空间中的所有服务账户。您必须为 groupIds 指定 1-5 个安全组 ID。如果指定了多个 ID,则所有安全组中的所有规则的组合都会对选定的 pods 生效。

      cat >my-security-group-policy.yaml <<EOF apiVersion: vpcresources.k8s.aws/v1beta1 kind: SecurityGroupPolicy metadata: name: my-security-group-policy namespace: my-namespace spec: podSelector: matchLabels: role: my-role securityGroups: groupIds: - $my_pod_security_group_id EOF
      重要

      您为 pod 指定的一个或多个安全组必须符合以下标准:

      • 它们必须存在。如果它们不存在,那么当您部署与选择器匹配的 pod 时,该 pod 会在创建过程中处于卡住状态。如果您描述 pod,则会看到类似于以下内容的错误消息:An error occurred (InvalidSecurityGroupID.NotFound) when calling the CreateNetworkInterface operation: The securityGroup ID 'sg-05b1d815d1EXAMPLE' does not exist

      • 它们必须允许通过您为其配置了探测器的任何端口从集群安全组进行入站通信(对于 kubelet)。

      • 它们必须允许通过 TCPUDP 端口 53 对分配到运行 CoreDNS 的 pods 的安全组的出站通信。适用于您的 CoreDNS pods 的安全组必须允许来自您的安全组的入站 TCPUDP 端口 53 流量。

      • 它们必须具备必要的入站和出站规则才能与它们需要与其通信的其他 pods 进行通信。

      • 如果您将安全组与 Fargate 一起使用,它们必须具有允许 pods 与 Kubernetes 控制面板通信的规则。执行此操作的最简单方法是将集群安全组指定为安全组之一。

      安全组策略仅适用于新调度的 pods。不影响正在运行的 pods。

    2. 部署策略。

      kubectl apply -f my-security-group-policy.yaml
  4. 部署其标注与上一步中指定的 podSelectormy-role 值相匹配的示例应用程序。

    1. 将以下内容复制到您的设备。将示例值替换为您自己的值,然后运行修改后的命令。

      cat >sample-application.yaml <<EOF apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment namespace: my-namespace labels: app: my-app spec: replicas: 4 selector: matchLabels: app: my-app template: metadata: labels: app: my-app role: my-role spec: terminationGracePeriodSeconds: 120 containers: - name: nginx image: public.ecr.aws/nginx/nginx:1.23 ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: my-app namespace: my-namespace labels: app: my-app spec: selector: app: my-app ports: - protocol: TCP port: 80 targetPort: 80 EOF
    2. 使用以下命令部署应用程序。当您部署应用程序时,Amazon VPC CNI plugin for Kubernetes 插件将匹配 role 标注,并且您在上一步中指定的安全组将应用到 pod。

      kubectl apply -f sample-application.yaml
  5. 查看使用示例应用程序部署的 pods。对于本主题的其余部分,此终端称为 TerminalA

    kubectl get pods -n my-namespace -o wide

    输出示例如下。

    NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES my-deployment-5df6f7687b-4fbjm 1/1 Running 0 7m51s 192.168.53.48 ip-192-168-33-28.region-code.compute.internal <none> <none> my-deployment-5df6f7687b-j9fl4 1/1 Running 0 7m51s 192.168.70.145 ip-192-168-92-33.region-code.compute.internal <none> <none> my-deployment-5df6f7687b-rjxcz 1/1 Running 0 7m51s 192.168.73.207 ip-192-168-92-33.region-code.compute.internal <none> <none> my-deployment-5df6f7687b-zmb42 1/1 Running 0 7m51s 192.168.63.27 ip-192-168-33-28.region-code.compute.internal <none> <none>
    注意
    • 如果任何 pods 卡在 Waiting 状态,则运行 kubectl describe pod my-deployment-xxxxxxxxxx-xxxxx -n my-namespace。如果您看到了 Insufficient permissions: Unable to create Elastic Network Interface.,请确认您已在上一步中将 IAM policy 添加到 IAM 集群角色。

    • 如果任何容器组 (pod) 卡在 Pending 状态,请确认您的节点实例类型已经列在 limits.go 中,并且尚未达到实例类型支持的最大分支网络接口数乘以您的节点组中节点数得到的数值。例如,m5.large 实例支持 9 个分支网络接口。如果节点组有 5 个节点,则最多可以为节点组创建 45 个分支网络接口。在删除另一个具有关联安全组的 pod 前,您尝试部署的第 46 个 pod 将会处于 Pending 状态。

    如果您运行 kubectl describe pod my-deployment-xxxxxxxxxx-xxxxx -n my-namespace 并看到类似于以下消息内容的消息,则可以安全地忽略该消息。当 Amazon VPC CNI plugin for Kubernetes 尝试在创建网络接口时设置主机联网并失败时,可能会出现此消息。该插件会记录此事件,直到创建了网络接口为止。

    Failed to create pod sandbox: rpc error: code = Unknown desc = failed to set up sandbox container "e24268322e55c8185721f52df6493684f6c2c3bf4fd59c9c121fd4cdc894579f" network for pod "my-deployment-5df6f7687b-4fbjm": networkPlugin cni failed to set up pod "my-deployment-5df6f7687b-4fbjm-c89wx_my-namespace" network: add cmd: failed to assign an IP address to container

    您不能超过可在实例类型上运行的 pods 的最大数量。有关可在每种实例类型上运行的 pods 的最大数量的列表,请参阅 GitHub 上的 eni-max-pods.txt。当您删除具有关联安全组的 pod 或删除运行该 pod 的节点时,VPC 资源控制器会删除分支网络接口。如果您使用适用于安全组的 pods 删除带有 pods 的集群,则该控制器不会删除分支网络接口,因此您需要自行删除它们。

  6. 在单独的终端中,shell 进入其中一个 pods。对于本主题的其余部分,此终端称为 TerminalB。将 5df6f7687b-4fbjm 替换为您在上一步输出中返回的其中一个 pods 的 ID。

    kubectl exec -it -n my-namespace my-deployment-5df6f7687b-4fbjm -- /bin/bash
  7. TerminalB 的 shell 中,确认示例应用程序是否有效。

    curl my-app

    输出示例如下。

    <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> ...

    您收到了输出,因为运行该应用程序的所有 pods 与您创建的安全组关联。该组包含一条规则,允许安全组所关联的所有 pods 之间的所有流量。允许 DNS 流量从该安全组出站到与您的节点关联的集群安全组。节点正在运行 CoreDNS pods,您的 pods 对该 DNS 进行了名称查找。

  8. TerminalA 中,删除允许从安全组与集群安全组进行 DNS 通信的安全组规则。如果您在上一步中没有将 DNS 规则添加到集群安全组中,请将 $my_cluster_security_group_id 替换为您在其中创建规则的安全组的 ID。

    aws ec2 revoke-security-group-ingress --group-id $my_cluster_security_group_id --security-group-rule-ids $my_tcp_rule_id aws ec2 revoke-security-group-ingress --group-id $my_cluster_security_group_id --security-group-rule-ids $my_udp_rule_id
  9. TerminalB 中,尝试再次访问该应用程序。

    curl my-app

    输出示例如下。

    curl: (6) Could not resolve host: my-app

    由于 pod 不再能够访问具有与其关联的集群安全组的 CoreDNS pods,尝试失败。集群安全组不再具有安全组规则,该规则允许从与 pod 关联的安全组进行 DNS 通信。

    如果您尝试使用上一步中的其中一个 pods 返回的 IP 地址访问应用程序,您仍然会收到响应,因为在与安全组相关联的所有 pods 之间允许所有端口,并且不需要名称查找。

  10. 完成实验后,您可以移除您创建的示例安全组策略、应用程序和安全组。从 TerminalA 运行以下命令。

    kubectl delete namespace my-namespace aws ec2 revoke-security-group-ingress --group-id $my_pod_security_group_id --security-group-rule-ids $my_inbound_self_rule_id wait sleep 45s aws ec2 delete-security-group --group-id $my_pod_security_group_id