Security Group for Pod - 实验

为了启用Security Group for Pod功能,EKS 集群在控制平面上运行了两个新组件:

  • 一个Mutating Webhook, 负责向需要安全组的pod添加限制和请求。
  • resource controller负责管理ENI与这些 pod 相关联。

为了实现此功能,每个worker node将与一个主干网络接口(trunk ENI)和多个分支网络接口(branch ENI)相关联。trunk ENI充当连接到实例的标准网络接口。然后,VPC resource controllerbranch ENI关联到trunk ENI。这增加了每个实例可以连接的网络接口的数量。由于安全组是通过ENI指定的,因此我们现在可以将需要特定安全组的 Pod 调度到分配给工作节点的这些附加网络接口上。

CNI配置

首先,需要将新的 IAM 策略附加到nodegroup Role,以允许 EC2 实例管理ENI、私有 IP 地址以及与实例的绑定和分离。

添加AmazonEKSVPCResourceController policy到节点组的Role:

aws iam attach-role-policy \
    --policy-arn arn:aws:iam::aws:policy/AmazonEKSVPCResourceController \
    --role-name ${ROLE_NAME}

image-20240204100110900

aws-node 中将变量ENABLE_POD_ENI设置为 true,启用 CNI 插件来管理 pod 的网络接口。

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

# let's wait for the rolling update of the daemonset
kubectl -n kube-system rollout status ds aws-node

image-20240204100800210

一旦此设置设置为 true,对于集群中的每个节点,插件都会添加一个带有vpc.amazonaws.com/has-trunk-attached=true的标签。 VPC resource controller创建并附加一个称为trunk ENI的特殊网络接口,其描述为 aws-k8s-trunk-eni

 kubectl get nodes --show-labels

image-20240204100829954

Security Group Policy

创建集群时还会自动添加新的CRD, 集群管理员可以通过SecurityGroupPolicy CRD 指定将哪些安全组分配给 pod 。在命名空间内,可以根据 pod 标签或与 pod 关联的service account的标签来选择 pod。对于任何匹配的 Pod,还可以定义要应用的安全组 ID。

验证 CRD 是否存在:

kubectl get crd securitygrouppolicies.vpcresources.k8s.aws

image-20240204101112180

Webhook会监视SecurityGroupPolicy CRD的任何更改,以便将 Pod 调度到具有可用branch ENI的节点上。一旦 Pod 被调度,resource controller将创建一个branch ENI并将其附加到trunk ENI。成功附加后,控制器会向 pod 对象添加一个带有branch ENI详细信息的注释。

现在让我们创建我们的策略。

cat << EoF > sg-policy.yaml
apiVersion: vpcresources.k8s.aws/v1beta1
kind: SecurityGroupPolicy
metadata:
  name: allow-rds-access
spec:
  podSelector:
    matchLabels:
      app: green-pod
  securityGroups:
    groupIds:
      - ${POD_SG}
EoF

如果 pod 具有标签app: green-pod,则会将安全组附加到它。

部署在特定的namespace:

kubectl create namespace sg-per-pod

kubectl -n sg-per-pod apply -f sg-policy.yaml

kubectl -n sg-per-pod describe securitygrouppolicy

image-20240204102000700

构建应用程序以连接到 RDS

接下来创建一个python应用,该应用连接到我们的 Postgres 数据库并在成功时输出信息,否则打印错误消息:

cat << EoF > postgres_test_iam.py
import os
import psycopg2

HOST = os.getenv('HOST')
PORT = "5432"
USER = os.getenv('USER')
REGION = "us-west-2"
DBNAME = os.getenv('DATABASE')
DBPASS = os.getenv('PASSWORD')

print(HOST,USER,DBNAME,DBPASS)

conn = None
try:
    conn = psycopg2.connect(host=HOST, port=PORT, database=DBNAME, user=USER, password=DBPASS, connect_timeout=3)
    cur = conn.cursor()
    cur.execute("""SELECT version()""")
    query_results = cur.fetchone()
    print(query_results)
    cur.close()
except Exception as e:
    print("Database connection failed due to {}".format(e))
finally:
    if conn is not None:
        conn.close()
EoF

将以下内容另存为Dockerfile:

cat << EoF > Dockerfile
FROM python:3.8.5-slim-buster
ADD postgres_test_iam.py /
RUN pip install psycopg2-binary
CMD [ "python", "-u", "./postgres_test_iam.py" ]
EoF

构建容器并将其推送到ECR 。使用自己的帐户 ID:

docker build -t postgres-test .
aws ecr create-repository --repository-name postgres-test-demo
aws ecr get-login-password | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.us-west-2.amazonaws.com
docker tag postgres-test ${AWS_ACCOUNT_ID}.dkr.ecr.us-west-2.amazonaws.com/postgres-test-demo:latest
docker push ${AWS_ACCOUNT_ID}.dkr.ecr.us-west-2.amazonaws.com/postgres-test-demo:latest

部署示例应用程序

现在让我们部署应用程序并测试只有所需的 Pod 才能访问我们的 RDS 数据库。将以下内容保存为 postgres-test.yaml。将 HOST、DATABASE 和 USER 环境变量替换为您在上面创建 RDS 数据库的步骤中的值。

cat << EoF > postgres-test.yaml
apiVersion: v1
kind: Pod
metadata:
  name: postgres-test
  labels:
    app: green-pod
  namespace: sg-per-pod
spec:
  containers:
  - name: postgres-test
    image: ${AWS_ACCOUNT_ID}.dkr.ecr.us-west-2.amazonaws.com/postgres-test-demo:latest
    env:
    - name: HOST
      value: ${RDS_ENDPOINT}
    - name: DATABASE
      value: eksworkshop
    - name: USER
      value: eksworkshop
    - name: PASSWORD
      value: ${RDS_PASSWORD}
EoF
kubectl apply -f postgres-test.yaml

让我们检查日志以确认该 pod 确实可以访问我们的 RDS 数据库。

kubectl logs postgres-test -n sg-per-pod

image-20240204105751195

现在我们来验证一下:

  • ENI 连接到 Pod。
  • ENI 附加了安全组 POD_SG。

我们可以使用此命令在 podAnnotations部分找到 ENI ID。

kubectl -n sg-per-pod  describe pod $GREEN_POD_NAME | head -11

输出:

image-20240204132836627

在控制台上,可以看到这个ENI关联到了POD_SG,并且属于branch ENI:

image-20240204134302059

我们稍微修改一下 Pod 配置, 更改下它的label。该 Pod 将不再与我们的Security group policy匹配,并且不应能够访问数据库。将以下内容保存为 postgres-test-wrong-label.yaml:

cat << EoF > postgres-test-wrong-label.yaml
apiVersion: v1
kind: Pod
metadata:
  name: postgres-test-wrong-label
  labels:
    app: red-pod
  namespace: sg-per-pod
spec:
  containers:
  - name: postgres-test
    image: ${AWS_ACCOUNT_ID}.dkr.ecr.us-west-2.amazonaws.com/postgres-test-demo:latest
    env:
    - name: HOST
      value: ${RDS_ENDPOINT}
    - name: DATABASE
      value: eksworkshop
    - name: USER
      value: eksworkshop
    - name: PASSWORD
      value: ${RDS_PASSWORD}
EoF
kubectl apply -f postgres-test-wrong-label.yaml
kubectl logs postgres-test-wrong-label -n sg-per-pod

image-20240204110140172

最后让我们验证 pod 有没有 annotation

kubectl describe po postgres-test-wrong-label -n sg-per-pod

输出:

image-20240204134619765

结论

在本模块中,我们在EKS上启用了Pod 的安全组功能。

我们创建了一个SecurityGroup策略并部署了 2 个 Pod(使用相同的 docker 映像)和一个受安全组保护的 RDS 数据库。

根据这一策略,两个 Pod 中只有一个能够连接到数据库。

最后,使用 CLI 和 AWS 控制台,我们能够找到 Pod 的 ENI 并验证安全组是否已附加上去。

清理资源

实验完成后记得删除数据库:

# delete database
aws rds delete-db-instance \
    --db-instance-identifier rds-eksworkshop \
    --delete-automated-backups \
    --skip-final-snapshot

参考

Security group for pod的具体原理参考: https://aws.amazon.com/cn/blogs/containers/introducing-security-groups-for-pods/

img