본문 바로가기

AWS EKS 실습/EKS Beginner

Security Groups For Pods

컨테이너화된 어플리케션은 Amazon RDS와 같은 외부 AWS 서비스뿐만 아니라 cluster 내에서 실행되는 다른 서비스간에 엑세스 하는 것이 자주 필요하다.

 

AWS에서 서비스 간의 네트워크 수준 액세스 제어는 종종 보안 그룹을 통해 수행 된다.

 

이 기능이 출시 되기 전에는 노드 수준에서만 보안 그룹을 할당할 수 있었다. 또한 노드 그룹 내부의 모든 노드가 보안 그룹을 공유하기 때문에 노드 그룹 보안 그룹이 RDS 인스턴스에 액세스 할 수 있도록 허용하여 녹색 Pod에서만 액세스 권한이 있어야 하는 경우에도 노드에서 실행중인 모든 Pod가 RDS에 액세스 엑세스 할 수 있다.

 

Security Groups for Pods는 Kubernetes Pods와 Securty Group을 통합한다. Amazon EC2 security group은 많은 Amazon EC2 Instance Type에서 운영되는 노드로 배포되는 Pod로 부터의 Inbound/outbound 네트워크 트래픽을 허용하는 룰을 정의한다.

 

ㅁ Seconds Node 생성

 

pods를 위한 Security Group은 m5, c5, r5, p3, m6g, c6g 그리고 r6를 포함하는 Nitro 기반 Amazon EC2 instance family에의해 지원된다. t3는 지원하지 않는다. 그래서 테스트를 위해서는 기존과 다르게 m5.large로 2번째 NodeGroup을 생성해야 한다.

 

[2nd NodeGroup 생성]

mkdir sg-per-pod

cat << EoF > ${HOME}/environment/sg-per-pod/nodegroup-sec-group.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: eks-newelite-eksctl
  region: ${AWS_REGION}

managedNodeGroups:
- name: nodegroup-sec-group
  desiredCapacity: 1
  instanceType: m5.large
EoF

eksctl create nodegroup -f ${HOME}/environment/sg-per-pod/nodegroup-sec-group.yaml

 kubectl get nodes \
  --selector beta.kubernetes.io/instance-type=m5.large

ㅁ Security Group 생성 및 구성

 

첫번째 RDS_SG라는 RDS Security 그룹을 생성한다. Amazon RDS에서 Network Access 제어에 사용된다.

 

[RDS Security Group 생성]

export VPC_ID=$(aws eks describe-cluster \
    --name eks-newelite-eksctl \
    --query "cluster.resourcesVpcConfig.vpcId" \
    --output text)

# create RDS security group
aws ec2 create-security-group \
    --description 'RDS SG' \
    --group-name 'RDS_SG' \
    --vpc-id ${VPC_ID}

# save the security group ID for future use
export RDS_SG=$(aws ec2 describe-security-groups \
    --filters Name=group-name,Values=RDS_SG Name=vpc-id,Values=${VPC_ID} \
    --query "SecurityGroups[0].GroupId" --output text)

echo "RDS security group ID: ${RDS_SG}"

[POD Security Group 생성]

# create the POD security group
aws ec2 create-security-group \
    --description 'POD SG' \
    --group-name 'POD_SG' \
    --vpc-id ${VPC_ID}

# save the security group ID for future use
export POD_SG=$(aws ec2 describe-security-groups \
    --filters Name=group-name,Values=POD_SG Name=vpc-id,Values=${VPC_ID} \
    --query "SecurityGroups[0].GroupId" --output text)

echo "POD security group ID: ${POD_SG}"

[Node Group Security Group 생성 및 DNS와의 TCP/UDP 53 포트 오픈]

export NODE_GROUP_SG=$(aws ec2 describe-security-groups \
    --filters Name=tag:Name,Values=eks-cluster-sg-eks-newelite-eksctl-* Name=vpc-id,Values=${VPC_ID} \
    --query "SecurityGroups[0].GroupId" \
    --output text)
echo "Node Group security group ID: ${NODE_GROUP_SG}"

# allow POD_SG to connect to NODE_GROUP_SG using TCP 53
aws ec2 authorize-security-group-ingress \
    --group-id ${NODE_GROUP_SG} \
    --protocol tcp \
    --port 53 \
    --source-group ${POD_SG}

# allow POD_SG to connect to NODE_GROUP_SG using UDP 53
aws ec2 authorize-security-group-ingress \
    --group-id ${NODE_GROUP_SG} \
    --protocol udp \
    --port 53 \
    --source-group ${POD_SG}

[RDS_SG Security Group에 Cloud9과 POD_SG Security Group 연결 허가]

# Cloud9 IP
export C9_IP=$(curl -s http://169.254.169.254/latest/meta-data/public-ipv4)

# allow Cloud9 to connect to RDS
aws ec2 authorize-security-group-ingress \
    --group-id ${RDS_SG} \
    --protocol tcp \
    --port 5432 \
    --cidr ${C9_IP}/32

# Allow POD_SG to connect to the RDS
aws ec2 authorize-security-group-ingress \
    --group-id ${RDS_SG} \
    --protocol tcp \
    --port 5432 \
    --source-group ${POD_SG}

 

ㅁ RDS Creation

 

PostgreSQL database를 위한 Amazon RDS를 생성한다.

 

[DB Subnet Group 생성]

export PUBLIC_SUBNETS_ID=$(aws ec2 describe-subnets \
    --filters "Name=vpc-id,Values=$VPC_ID" "Name=tag:Name,Values=eksctl-eks-newelite-eksctl-cluster/SubnetPublic*" \
    --query 'Subnets[*].SubnetId' \
    --output json | jq -c .)

# create a db subnet group
aws rds create-db-subnet-group \
    --db-subnet-group-name rds-eks-newelite \
    --db-subnet-group-description rds-eks-newelite \
    --subnet-ids ${PUBLIC_SUBNETS_ID}

[RDS DB 생성]

# get RDS SG ID
export RDS_SG=$(aws ec2 describe-security-groups \
    --filters Name=group-name,Values=RDS_SG Name=vpc-id,Values=${VPC_ID} \
    --query "SecurityGroups[0].GroupId" --output text)

# generate a password for RDS
export RDS_PASSWORD="$(date | md5sum  |cut -f1 -d' ')"
echo ${RDS_PASSWORD}  > ~/environment/sg-per-pod/rds_password


# create RDS Postgresql instance
aws rds create-db-instance \
    --db-instance-identifier rds-eks-newelite \
    --db-name eksnewelite \
    --db-instance-class db.t3.micro \
    --engine postgres \
    --db-subnet-group-name rds-eks-newelite \
    --vpc-security-group-ids $RDS_SG \
    --master-username eksnewelite \
    --publicly-accessible \
    --master-user-password ${RDS_PASSWORD} \
    --backup-retention-period 0 \
    --allocated-storage 20

 

[DB 생성 상태 Verify]

aws rds describe-db-instances \
    --db-instance-identifier rds-eks-newelite \
    --query "DBInstances[].DBInstanceStatus" \
    --output text

아래와 같이 available이 나오면 정상으로 생성되었다는 것이다.

available

[DB Endpoint 확인]

# get RDS endpoint
export RDS_ENDPOINT=$(aws rds describe-db-instances \
  --db-instance-identifier rds-eks-newelite \
  --query 'DBInstances[0].Endpoint.Address' \
  --output text)
  
echo "RDS endpoint: ${RDS_ENDPOINT}"

아래의 주소 확인됨

rds-eks-newelite.c3mt9mp5gdta.ap-northeast-2.rds.amazonaws.com

 

[테스트 접속 및 일부 데이터 생성]

sudo yum install -y postgresql

cd sg-per-pod

cat << EoF > ~/environment/sg-per-pod/pgsql.sql
CREATE TABLE welcome (column1 TEXT);
insert into welcome values ('--------------------------');
insert into welcome values ('Welcome to the eksworkshop');
insert into welcome values ('--------------------------');
EoF

export RDS_PASSWORD=$(cat ~/environment/sg-per-pod/rds_password)

psql postgresql://eksnewelite:${RDS_PASSWORD}@${RDS_ENDPOINT}:5432/eksnewelite \
    -f ~/environment/sg-per-pod/pgsql.sql

ㅁ CNI(Container Network Interface) Configuration

 

이 새로운 기능을 적용하려면 kubernetes control plane에 새로운 2개의 component가 추가되어야 한다.

 

- mutatin webhook (Security Group이 필요로하는 Pods에 대한 제한 및 요청을 추가하는 역할) 

- resource controller (Pods완 연결된 Network Inteface를 관리하는 역할)

 

이 기능을 적용시키려면 각 worker node는 싱글 Trunk Network Interface와 여러 branch Network Interface로 연결됨

Trunk Interface는 인스턴스에 연결된 표준 네트워크 인터페이스 역할을 하며 VPC 리로스 컨트롤러는 branch Interface를 trunk interface에 연결한다. 이렇게 하면 인스턴스당 연결 할 수 있는 네트워크 인터페이스 수가 늘어난다.

 

보안 그룹이 네트워크 인터페이스로 지정되었으므로 Worker Node에 할당된 추가 network inteface에 특정 보안 그룹이 필요한 Pod를 스케줄 할 수 있다.

 

먼저 EC2 인스턴스가 Network Interface, Private IP Address 및 Instance와의 연결(attach) 및 분리(detach)를 관리할 수 있도록 새로운 IAM Policy를 Node Group Role로 연결해야 한다.

 

[cluster role에 AmazonEKSVPCResourceController 정책 추가]

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

aws-node DaemonSet에 ENABLE_POD_ENI를 True로 설정하여 Pods에 Network Inteface를 관리하기 위한 CNI 플러그인을 Enable 한다. 

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

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

이 Ture 설정은 클러스터의 각 노드에 플러그인을 적합한 Instance에  vpc.amazonaws.com/has-trunk-attached=ture로 라벨을 추가한다. VPC Resource controllersms description이 aws-k8s-trunk-eni로 설정된 trunk network interface라는 특수 network interface 하나를 생성하고 연결한다. 

 

 kubectl get nodes \
  --selector alpha.eksctl.io/nodegroup-name=nodegroup-sec-group \
  --show-labels

ㅁ SecurityGroup Policy

 

새로운 CRD(Customer Resource Definition)은 Cluster 생성 시 자동으로 추가된다. Cluster 어드민은 SecurityGroupPolicy CRD을 통해 security group을 pods에 할당할 수 있다. 

네임스페이스 내에서 Pod 라벨 또는 Pod와 연결된 Service Account의 라벨을 기준으로 Pod를 선택할 수 있고 일치하는 Pod에 대해 적용할 security group ID도 정의한다.

 

아래 명령을 통해 CRD가 있는지 확인할 수 있다.

kubectl get crd securitygrouppolicies.vpcresources.k8s.aws

webhook는 SecurityGroupPolicy CRD에서 변경사항을 감시하고, 포드를 스케줄하기 위해 요구되어지는 확장 리소스 요청이 있는 Pod를 사용 가능한 branch network interface 용량이 있는 노드로 자동으로 삽입한다. 

Pod가 스케줄되면 리소스 컨트롤러가 branch interface를 생성하여 trunk interface에 연결한다. 성공적으로 연결되면 컨트롤러는 branch interface 세부정보와 함께 pod 개체에 주석을 추가한다.

cat << EoF > ~/environment/sg-per-pod/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의 Label을 app:green-pod로 해 seucirty group에 연결한다.

마지막으로 특정 namespace로 배포한다.

 

kubectl create namespace sg-per-pod

kubectl -n sg-per-pod apply -f ~/environment/sg-per-pod/sg-policy.yaml

kubectl -n sg-per-pod describe securitygrouppolicy

 

 

ㅁ Pods Deployments

 

[RDS endpoint 와 Password를 kubernetes Secret으로 제공]

 

export RDS_PASSWORD=$(cat ~/environment/sg-per-pod/rds_password)

export RDS_ENDPOINT=$(aws rds describe-db-instances \
    --db-instance-identifier rds-eks-newelite \
    --query 'DBInstances[0].Endpoint.Address' \
    --output text)

kubectl create secret generic rds\
    --namespace=sg-per-pod \
    --from-literal="password=${RDS_PASSWORD}" \
    --from-literal="host=${RDS_ENDPOINT}"

kubectl -n sg-per-pod describe  secret rds

 

[Deployment 수행]

cd ~/environment/sg-per-pod

curl -s -O https://www.eksworkshop.com/beginner/115_sg-per-pod/deployments.files/green-pod.yaml
curl -s -O https://www.eksworkshop.com/beginner/115_sg-per-pod/deployments.files/red-pod.yaml

- green-pod.yaml

더보기
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: green-pod
  name: green-pod
  namespace: sg-per-pod
spec:
  replicas: 1
  selector:
    matchLabels:
      app: green-pod
  template:
    metadata:
      labels:
        app: green-pod
    spec:
      affinity:
       nodeAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
          nodeSelectorTerms:
          - matchExpressions:
            - key: "vpc.amazonaws.com/has-trunk-attached"
              operator: In
              values:
                - "true"
      containers:
      - image: fmedery/app:latest
        name: green-pod
        resources:
          requests:
            memory: "256Mi"
            cpu: "500m"
          limits:
            memory: "512Mi"
            cpu: "1024m"
        env:
        - name: HOST
          valueFrom:
            secretKeyRef:
              name: rds
              key: host
        - name: DBNAME
          value: eksnewelite
        - name: USER
          value: eksnewelite
        - name: PASSWORD
          valueFrom:
            secretKeyRef:
              name: rds
              key: password

- red-pod.yaml

더보기
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: red-pod
  name: red-pod
  namespace: sg-per-pod
spec:
  replicas: 1
  selector:
    matchLabels:
      app: red-pod
  template:
    metadata:
      labels:
        app: red-pod
    spec:
      affinity:
       nodeAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
          nodeSelectorTerms:
          - matchExpressions:
            - key: "vpc.amazonaws.com/has-trunk-attached"
              operator: In
              values:
                - "true"
      containers:
      - image: fmedery/app:latest
        name: red-pod
        resources:
          requests:
            memory: "256Mi"
            cpu: "500m"
          limits:
            memory: "512Mi"
            cpu: "1024m"
        env:
        - name: HOST
          valueFrom:
            secretKeyRef:
              name: rds
              key: host
        - name: DBNAME
          value: eksnewelite
        - name: USER
          value: eksnewelite
        - name: PASSWORD
          valueFrom:
            secretKeyRef:
              name: rds
              key: password

 

[Green Pod 배포]

kubectl -n sg-per-pod apply -f ~/environment/sg-per-pod/green-pod.yaml

kubectl -n sg-per-pod rollout status deployment green-pod

- log 확인

export GREEN_POD_NAME=$(kubectl -n sg-per-pod get pods -l app=green-pod -o jsonpath='{.items[].metadata.name}')

kubectl -n sg-per-pod  logs -f ${GREEN_POD_NAME}

[Green POD의 ENI ID 확인]

 

kubectl -n sg-per-pod  describe pod $GREEN_POD_NAME | head -11
eksuser:~/environment/sg-per-pod $ kubectl -n sg-per-pod  describe pod $GREEN_POD_NAME | head -11
Name:         green-pod-676f5fc9dd-b9zf9
Namespace:    sg-per-pod
Priority:     0
Node:         ip-192-168-11-106.ap-northeast-2.compute.internal/192.168.11.106
Start Time:   Fri, 26 Feb 2021 06:30:43 +0000
Labels:       app=green-pod
              pod-template-hash=676f5fc9dd
Annotations:  kubernetes.io/psp: eks.privileged
              vpc.amazonaws.com/pod-eni:
                [{"eniId":"eni-04ee636ffb650871a","ifAddress":"02:1b:08:65:ea:ae","privateIp":"192.168.9.232","vlanId":2,"subnetCidr":"192.168.0.0/19"}]

여기서 eniID는 eni-04ee636ffb650871a 이므로 AWS Console에서 해당 ID 값의 Network Interface 확인

 

[Red Pod 배포]

위에서 SG Policy 적용시 green-pod 만 적용되록 설정되어 이 배포는 정상적으로 DB에 연결되지 않는다.

kubectl -n sg-per-pod apply -f ~/environment/sg-per-pod/red-pod.yaml

kubectl -n sg-per-pod rollout status deployment red-pod
export RED_POD_MAME=$(kubectl -n sg-per-pod get pods -l app=red-pod -o jsonpath='{.items[].metadata.name}')

kubectl -n sg-per-pod  logs -f ${RED_POD_MAME}

 

annotation에 eniId가 없는 것을 볼 수 있다.

eksuser:~/environment/sg-per-pod $ kubectl -n sg-per-pod  describe pod ${RED_POD_MAME} | head -11
Name:         red-pod-58745dd4b8-58q9w
Namespace:    sg-per-pod
Priority:     0
Node:         ip-192-168-11-106.ap-northeast-2.compute.internal/192.168.11.106
Start Time:   Fri, 26 Feb 2021 06:39:14 +0000
Labels:       app=red-pod
              pod-template-hash=58745dd4b8
Annotations:  kubernetes.io/psp: eks.privileged
Status:       Running
IP:           192.168.18.210
IPs:

ㅁ CleanUp Script

export VPC_ID=$(aws eks describe-cluster \
    --name eks-newelite-eksctl \
    --query "cluster.resourcesVpcConfig.vpcId" \
    --output text)
export RDS_SG=$(aws ec2 describe-security-groups \
    --filters Name=group-name,Values=RDS_SG Name=vpc-id,Values=${VPC_ID} \
    --query "SecurityGroups[0].GroupId" --output text)
export POD_SG=$(aws ec2 describe-security-groups \
    --filters Name=group-name,Values=POD_SG Name=vpc-id,Values=${VPC_ID} \
    --query "SecurityGroups[0].GroupId" --output text)
export C9_IP=$(curl -s http://169.254.169.254/latest/meta-data/public-ipv4)
export NODE_GROUP_SG=$(aws ec2 describe-security-groups \
    --filters Name=tag:Name,Values=eks-cluster-sg-eks-newelite-eksctl-* Name=vpc-id,Values=${VPC_ID} \
    --query "SecurityGroups[0].GroupId" \
    --output text)

# uninstall the RPM package
sudo yum erase -y postgresql

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

# delete kubernetes element
kubectl -n sg-per-pod delete -f ~/environment/sg-per-pod/green-pod.yaml
kubectl -n sg-per-pod delete -f ~/environment/sg-per-pod/red-pod.yaml
kubectl -n sg-per-pod delete -f ~/environment/sg-per-pod/sg-policy.yaml
kubectl -n sg-per-pod delete secret rds

# delete the namespace
kubectl delete ns sg-per-pod

# disable ENI trunking
kubectl -n kube-system set env daemonset aws-node ENABLE_POD_ENI=false
kubectl -n kube-system rollout status ds aws-node

# detach the IAM policy
aws iam detach-role-policy \
    --policy-arn arn:aws:iam::aws:policy/AmazonEKSVPCResourceController \
    --role-name ${ROLE_NAME}

# remove the security groups rules
aws ec2 revoke-security-group-ingress \
    --group-id ${RDS_SG} \
    --protocol tcp \
    --port 5432 \
    --source-group ${POD_SG}

aws ec2 revoke-security-group-ingress \
    --group-id ${RDS_SG} \
    --protocol tcp \
    --port 5432 \
    --cidr ${C9_IP}/32

aws ec2 revoke-security-group-ingress \
    --group-id ${NODE_GROUP_SG} \
    --protocol tcp \
    --port 53 \
    --source-group ${POD_SG}

aws ec2 revoke-security-group-ingress \
    --group-id ${NODE_GROUP_SG} \
    --protocol udp \
    --port 53 \
    --source-group ${POD_SG}

# delete POD security group
aws ec2 delete-security-group \
    --group-id ${POD_SG}

[RDS instance 삭제 확인]

aws rds describe-db-instances \
    --db-instance-identifier rds-eks-newelite \
    --query "DBInstances[].DBInstanceStatus" \
    --output text

[DB Security Group과 DB Subnet Group 삭제]

# delete RDS SG
aws ec2 delete-security-group \
    --group-id ${RDS_SG}

# delete DB subnet group
aws rds delete-db-subnet-group \
    --db-subnet-group-name rds-eks-newelite

[EKS NodeGroup 삭제]

# delete the nodegroup
eksctl delete nodegroup -f ${HOME}/environment/sg-per-pod/nodegroup-sec-group.yaml --approve

# remove the trunk label
kubectl label node  --all 'vpc.amazonaws.com/has-trunk-attached'-

cd ~/environment
rm -rf sg-per-pod

'AWS EKS 실습 > EKS Beginner' 카테고리의 다른 글

Exposing a Service  (0) 2021.02.26
EKS Network 정책 실습 (w/Calico)  (0) 2021.02.26
Create An OIDC Identity Provider  (0) 2021.02.25
Kubernetes access 관리를 위한 IAM Groups 사용  (1) 2021.02.25
Intro to RBAC  (0) 2021.02.24