EKSを理解する(第3回)Pod単位のセキュリティグループ割り当て

2020.11.27 Cloud

こんにちは、クラウド推進チームの宮國です。

 

今回もEKSに関する内容です。

 

先日のアップデートによって、Pod単位でセキュリティグループがアタッチできるようになりました。

 

※従来セキュリティグループはノード単位でしか割り振ることができなかったため、Pod単位でのアクセス制御をするためには、Calicoを別途導入して、Kubernetes のNetworkPolicyリソースを利用して定義する事で、ネットワークトラフィックを制御していました。

 

まだ試したことが無かったため、今回はAWSブログ公式ドキュメントを参考にしながら実際にPod単位でのセキュリティグループ割り当てを実施し、RDSへの接続を試してみようと思います。

 

クラスタ接続環境準備

まずはクラスタ構築用の環境としてCloud9を作成します。
今回はオレゴン(us-west-2)リージョンを使用します。

 

OSはAmazon Linux 2でVPCはdefaultとします。

 

後続の作業でDockerを利用する際に容量が不足するので、予めボリュームサイズを増やし、ファイルシステムを拡張しておきます。

 

df -hT
lsblk
sudo growpart /dev/xvda 1
lsblk
df -h
sudo xfs_growfs -d /
df -h

 

また、AdministratorAccess権限をもつIAMロールを作成し、Cloud9にアタッチします。

そしてCloud9がIAMロールを参照するように、AWS managed temporary credentialsをオフにします。

 

kubectlやeksctl等、各種コマンドをインストールします。

 

sudo curl --silent --location -o /usr/local/bin/kubectl \
   https://amazon-eks.s3.cn-north-1.amazonaws.com.cn/1.18.8/2020-09-18/bin/linux/amd64/kubectl


sudo chmod +x /usr/local/bin/kubectl


sudo pip install --upgrade awscli && hash -r


sudo curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
sudo mv -v /tmp/eksctl /usr/local/bin
eksctl version

 

このあたりの手順は主にEKS Workshopから抜粋しています。

 

クラスタ作成

Cloud9の準備ができたら、クラスタを作成します。

 

まず、クラスタの構成情報をyamlで定義します。この時、IRSAを利用するので、withOIDCをtrueにします。Kubernetesのバージョンも1.18と指定します。

 

eks.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
 
metadata:
  name: sgp-cluster
  region: us-west-2
  version: "1.18"
  
iam:
  withOIDC: true
 
managedNodeGroups:
  - name: nodegroup
    instanceType: m5.large
    desiredCapacity: 1
    privateNetworking: true

※yamlの書き方はこちらが参考になります。

 

次に、クラスタを作成します。
eksctl create cluster -f cluster.yml

 

作成できたら、EKSのクラスタロールにAmazonEKSVPCResourceControllerがアタッチされていることを確認します。このIAMポリシーは、セキュリティグループをポッドに適用する上で前提条件となっているようです。

 

セキュリティグループ作成

公式ブログを参考にしながら、Podに割り当てる用のセキュリティグループを作成します。
VPCID=$(aws eks describe-cluster --name sgp-cluster \
--query "cluster.resourcesVpcConfig.vpcId" \
--region us-west-2 \
--output text)


echo $VPCID

 

RDSSG=$(aws ec2 create-security-group --group-name RDSDbAccessSG \
   --description "Security group to apply to apps that need access to RDS" --vpc-id $VPCID \
   --region us-west-2 \
   --query "GroupId" \
   --output text)


echo $RDSSG

 

次にRDSに割り当てる用のセキュリティグループを作成します。
この時、EKSクラスタと同じVPCに作成します。
また、インバウンドとしてPodに割り当てる用のセキュリティグループと、作業しているCloud9のIPからのアクセスを許可します。

 

 

RDS作成

セキュリティグループが作成できたら、RDS(PostgreSQL)を作成します。

 

公式ドキュメントを参考に作成していきます。

 

この時、EKSクラスタと同じVPCにRDSを作成し、先ほど作成したセキュリティグループを割り当てます。(下のエビだと新規作成になっていますが、既存の選択が正しいです)また、IAM認証を有効化し、追加設定からデータベースを作成(testdb)しておきます。
また、別VPCとなるCloud9からPostgreSQLへアクセスしたいので、パブリックアクセス可能で「あり」を選択します。
※本来なら同VPC内に踏み台サーバを立ててRDSにアクセスさせる or VPC Peering するべきですが、手順簡略化のためこうしました。

 

 

 

RDSが作成できたら、IAM認証を利用するデータベースアカウントを作成するため、踏み台のCloud9からPostgreSQLにアクセスします。

 

 

sudo yum -y install postgresql.x86_64


psql \
   -h database-1.xxxxxxxxxx.us-west-2.rds.amazonaws.com \
   -p 5432 \
   -U postgres \
   -d testdb

CREATE USER db_userx; 
GRANT rds_iam TO db_userx;
\q

PodにIRSAを割り当てる準備

RDSの準備ができたらEKSの作業に戻ります。

 

RDSにアクセスする用のIRSAを作成するために、まず最小権限を持つIAMポリシーを作成します。AWS管理ポリシーを使うのであれば飛ばして良いと思います。
IAMデータベース認証を使用してDBインスタンスに接続するための最小権限は公式ドキュメントを参照し、次のようなポリシーを作成します。

 

 

 

リソースIDはRDS-データベース-設定-インスタンスから確認できます。

 

 

IAMポリシーが作成できたら、そのポリシーを利用してIRSAを作成します。
※IRSAに関しては、前回の記事でも触れています。

 

Pod単位でIAMロールを割り当てることで、認証レイヤでRDSへのアクセスを制御することが狙いです。
attachPolicyARNsは、先ほど作成したIAMポリシーのARNで置換します。
serviceaccount.yaml

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
 
metadata:
  name: sgp-cluster
  region: us-west-2
 
iam:
  withOIDC: true
  serviceAccounts:
  - metadata:
      name: rds-db-access
      namespace: default
      labels: {role: "backend"}
    attachPolicyARNs:
    - "arn:aws:iam::ACCOUNTID:policy/rds-auth-for-pod"

 

eksctl create iamserviceaccount -f serviceaccount.yaml --approve

 

yaml適用後CloudFormationを見ると、
IAMロールが作成されていることを確認できます。

 

また、kubernetesのdefault名前空間に、このIAMロールと紐づいたServiceAccountが作成されていることが確認できます。
kubectl describe sa  | grep eksctl -3


Name:                rds-db-access
Namespace:           default
Labels:              role=backend
Annotations:         eks.amazonaws.com/role-arn: arn:aws:iam::<ACCOUNTID>:role/eksctl-sgp-cluster-addon-iamserviceaccount-XXXXXXXXXX
~~~

 

Podにセキュリティグループを割り当てる準備

 

ここからはPodにセキュリティグループを割り当てるための設定を行います。
Pod単位でセキュリティグループを割り当てることで、ネットワークレイヤでRDSへのアクセスを制御することが狙いです。

 

まずはポッドのセキュリティグループを有効化します。

 

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

 

今回は飛ばしますが、livenessprobe、readinessprobeを利用する場合は、
kubectl edit daemonset aws-node -n kube-system
でDISABLE_TCP_EARLY_DEMUXをtrueにします。

 

 

そしてクラスタセキュリティグループ(eks-cluster-sg-xxxxxxxx)と、RDS接続用に先ほど作成したセキュリティグループのIDを取得し、yamlファイルを置換します。

 

CLUSTERSG=$(aws eks describe-cluster --name sgp-cluster \
   --query "cluster.resourcesVpcConfig.clusterSecurityGroupId" \
   --region us-west-2 \
   --output text)
# print security group IDs
echo $CLUSTERSG $RDSSG

sgp-policy.yaml

apiVersion: vpcresources.k8s.aws/v1beta1
kind: SecurityGroupPolicy
metadata:
  name: my-sg-policy
spec:
  serviceAccountSelector: 
    matchLabels: 
      role: backend
  securityGroups:
    groupIds: 
      - sg-yyyyyy
      - sg-zzzzzz

 

kubectl apply -f sgp-policy.yaml

 

Dockerイメージ準備

次に、別途フォルダを切って、その中にRDS接続用のサンプルアプリケーションとdockerfileを作成します。ここも公式ブログから拝借しています。

 

postgres_test_iam.py
import os
 
import boto3
import psycopg2
 
HOST = os.getenv('HOST')
PORT = "5432"
USER = os.getenv('USER')
REGION = "us-west-2"
DBNAME = os.getenv('DATABASE')
 
session = boto3.Session()
client = boto3.client('rds', region_name=REGION)
 
token = client.generate_db_auth_token(DBHostname=HOST, Port=PORT, DBUsername=USER, Region=REGION)
 
conn = None
try:
    conn = psycopg2.connect(host=HOST, port=PORT, database=DBNAME, user=USER, password=token, 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()

 

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

 

そして、dockerイメージを作成し、ECRにリポジトリを作成してイメージをpushします。
cd <フォルダ名>
docker build -t postgres-test .
aws ecr create-repository --repository-name postgres-test-demo --region us-west-2
aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin <ACCOUNTID>.dkr.ecr.us-west-2.amazonaws.com
docker tag postgres-test <ACCOUNTID>.dkr.ecr.us-west-2.amazonaws.com/postgres-test-demo:latest
docker push <ACCOUNTID>.dkr.ecr.us-west-2.amazonaws.com/postgres-test-demo:latest

 

Podのapply

ECRにイメージがpushできたら、イメージのURIをコピーして、いよいよpodを作成してapplyします。

 

postgres-test.yaml
apiVersion: v1
kind: Pod
metadata:
  name: postgres-test
spec:
  serviceAccountName: rds-db-access
  containers:
  - name: postgres-test
    image: <コピーしたイメージURI>
    env:
    - name: HOST
      value: "RDSのエンドポイント"
    - name: DATABASE
      value: "testdb"
    - name: USER
      value: "db_userx"

 

cd ../
kubectl apply -f postgres-test.yaml

ログを確認すると、RDSに接続できていることが確認できます。
kubectl logs postgres-test


('PostgreSQL 12.4 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-11), 64-bit',)

 

 

次に、サービスアカウントの個所をコメントアウトして再度applyしてみます。
postgres-test.yaml
apiVersion: v1
kind: Pod
metadata:
  name: postgres-test
spec:
#   serviceAccountName: rds-db-access
  containers:
  - name: postgres-test
    image: <コピーしたイメージURI>
    env:
    - name: HOST
      value: "RDSのエンドユーザエンドポイント"
    - name: DATABASE
      value: "testdb"
    - name: USER
      value: "db_userx"

 

 

kubectl delete -f postgres-test.yaml
kubectl apply -f postgres-test.yaml

 

 

 

rds-db-accessというサービスアカウントをデタッチすることで、セキュリティグループもIAMロールも紐づかなくなるため、想定通り接続できませんでした。

kubectl logs postgres-test
Database connection failed due to timeout expired

 

また、ラベルを付与せずに(≒セキュリティグループが当たらない)IRSAを作成し、Podに付与しても同じように接続できませんでしたし、
postgres-test.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
 
metadata:
  name: sgp-cluster
  region: us-west-2
 
iam:
  withOIDC: true
  serviceAccounts:
  - metadata:
      name: rds-db-access
      namespace: default
    #   labels: {role: "backend"}
    attachPolicyARNs:
    - "arn:aws:iam::775154630116:policy/rds-auth-for-pod"

 

ラベルは付与しているけれど、IAMロールをアタッチしていないサービスアカウントを作成し、Podに付与しても同様でした。

no-iam-sa.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: no-iam-sa
  namespace: default
  labels:
    app: backend

 

クリーニング

RDS、RDSに割り当てていたセキュリティグループ、ECRリポジトリ、podに割り当てたRDS接続用セキュリティグループ(RDSDbAccessSG)を消した上で、Cloud9から eksctl delete cluster -f cluster.yaml コマンドを打ち、クラスタとVPC等の関連リソースを削除します。

※順番を誤って消し残しがあった場合はCloudFormationからスタック-リソースを確認して手動でリソースを削除します。

最後にCloud9を削除します。

まとめ

このように、Pod単位でセキュリティグループとIAMロールを割り当てることで、よりきめ細かいアクセス制御が実現できることを確認できました。

 

是非実務でも取り入れていきたいなと思います。
今回は以上です。