들어가며
이번 세미나에서는 EKS 환경에서의 Automation에 대해서 학습했다. AWS에서 제공하는 컨트롤러를 통해 리소스들을 K8s의 리소스처럼 사용하는 방법을 알게 되었다. 이전 세미나에서 배운 보안 개념(IRSA)이 기반이 되어 세미나 내용을 잘 따라갈 수 있었다.
(기적의 세미나 커리큘럼은 무엇인가..)
ACK(AWS Controller for K8s)
ACK는 AWS 리소스를 k8s에서 직접 정의하고 사용할 수 있도록 개발한 오픈소스 프로젝트이다.
AWS에 익숙하지 않은 사용자(예: 플랫폼 엔지니어)가 k8s native 하게 원하는 리소스를 정의하고 사용할 수 있도록 제공할 수 있으며,
그림과 같이 k8s에서 리소스를 관리하는 형태와 흡사하게 AWS리소스를 사용할 수 있다.
출처 - 링크
사용자가 yaml파일에 AWS 리소스를 정의하여 kube-apiserver를 호출하면 ack-xxx-controller가 AWS에 리소스를 생성/수정/삭제 한다. 리소스의 상태가 변경될 경우 k8s에도 해당 내용이 갱신된다.
아래의 링크에서 ACK가 지원하는 여러 AWS 서비스를 확인할 수 있다. (ACK는 23년 6월 현재 17개의 정식 서비스를 제공하고 있다.)
ACK를 사용하여 AWS리소스를 생성하려면 당연히 k8s API 와 AWS API의 서비스 권한이 필요하다.
ACK는 k8s의 SA와 연결된 IRSA를 사용하여 AWS API의 서비스를 통해 리소스에 접근하는 권한을 관리한다.
ACK 실습
ACK 실습 과정은 다음과 같은 단계 따른다.
실습 - ACK를 이용한 S3 제어
Step 1: ack-s3-controller 생성
# 서비스명 변수 지정
export SERVICE=s3
# helm 차트 다운로드
export RELEASE_VERSION=$(curl -sL https://api.github.com/repos/aws-controllers-k8s/$SERVICE-controller/releases/latest | grep '"tag_name":' | cut -d'"' -f4 | cut -c 2-)
helm pull oci://public.ecr.aws/aws-controllers-k8s/$SERVICE-chart --version=$RELEASE_VERSION
tar xzvf $SERVICE-chart-$RELEASE_VERSION.tgz
# helm chart 확인
tree ~/$SERVICE-chart
# ACK S3 Controller 설치
export ACK_SYSTEM_NAMESPACE=ack-system
export AWS_REGION=ap-northeast-2
helm install --create-namespace -n $ACK_SYSTEM_NAMESPACE ack-$SERVICE-controller --set aws.region="$AWS_REGION" ~/$SERVICE-chart
# 설치 확인
helm list --namespace $ACK_SYSTEM_NAMESPACE
kubectl -n ack-system get pods
kubectl get crd | grep $SERVICE
kubectl get all -n ack-system
kubectl get-all -n ack-system
kubectl describe sa -n ack-system ack-s3-controller
설치가 끝나면 다음과 같이 ack-s3-controller 관련 k8s 리소스들이 생성된 것을 확인할 수 있다.
Step 2: S3에 대한 IRSA 설정
# Create an iamserviceaccount - AWS IAM role bound to a Kubernetes service account
eksctl create iamserviceaccount \
--name ack-$SERVICE-controller \
--namespace ack-system \
--cluster $CLUSTER_NAME \
--attach-policy-arn $(aws iam list-policies --query 'Policies[?PolicyName==`AmazonS3FullAccess`].Arn' --output text) \
--override-existing-serviceaccounts --approve
# 확인 >> 웹 관리 콘솔에서 CloudFormation Stack >> IAM Role 확인
eksctl get iamserviceaccount --cluster $CLUSTER_NAME | grep ack
# Inspecting the newly created Kubernetes Service Account, we can see the role we want it to assume in our pod.
kubectl get sa -n ack-system
kubectl describe sa ack-$SERVICE-controller -n ack-system
# Restart ACK service controller deployment using the following commands.
kubectl -n ack-system rollout restart deploy ack-$SERVICE-controller-$SERVICE-chart
# IRSA 적용으로 Env, Volume 추가 확인
kubectl describe pod -n ack-system -l k8s-app=$SERVICE-chart
IRSA를 통해 ENV와 Volumes 정보가 설정된다.
Step 3: S3 리소스 제어
# [터미널1] 모니터링
watch -d aws s3 ls
# S3 버킷 생성을 위한 설정 파일 생성
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)
# S3 버킷을 유일하게 생성하기 위해 사용자 계정 ID를 버킷명에 포함
export BUCKET_NAME=my-ack-s3-bucket-$AWS_ACCOUNT_ID
read -r -d '' BUCKET_MANIFEST <<EOF
apiVersion: s3.services.k8s.aws/v1alpha1
kind: Bucket
metadata:
name: $BUCKET_NAME
spec:
name: $BUCKET_NAME
EOF
echo "${BUCKET_MANIFEST}" > bucket.yaml
cat bucket.yaml | yh
# S3 버킷 생성
aws s3 ls
kubectl create -f bucket.yaml
bucket.s3.services.k8s.aws/my-ack-s3-bucket-<my account id> created
# S3 버킷 확인
aws s3 ls
kubectl get buckets
kubectl describe bucket/$BUCKET_NAME | head -6
Name: my-ack-s3-bucket-<my account id>
Namespace: default
Labels: <none>
Annotations: <none>
API Version: s3.services.k8s.aws/v1alpha1
Kind: Bucket
aws s3 ls | grep $BUCKET_NAME
2022-04-24 18:02:07 my-ack-s3-bucket-<my account id>
# S3 버킷 업데이트 : 태그 정보 입력
read -r -d '' BUCKET_MANIFEST <<EOF
apiVersion: s3.services.k8s.aws/v1alpha1
kind: Bucket
metadata:
name: $BUCKET_NAME
spec:
name: $BUCKET_NAME
tagging:
tagSet:
- key: myTagKey
value: myTagValue
EOF
echo "${BUCKET_MANIFEST}" > bucket.yaml
# S3 버킷 설정 업데이트 실행 : 필요 주석 자동 업뎃 내용이니 무시해도됨!
kubectl apply -f bucket.yaml
ack-s3-controller 를 통해 생성된 자원이 조회되는지 확인해 보았다.
이제 버킷 정보가 잘 업데이트 되는지 확인한다.
# S3 버킷 업데이트 확인
kubectl describe bucket/$BUCKET_NAME | grep Spec: -A5
Step 4: ACK S3 Controller 삭제
이제 생성된 버킷을 먼저 삭제한다.
# S3 버킷 삭제
kubectl delete -f bucket.yaml
# verify the bucket no longer exists
kubectl get bucket/$BUCKET_NAME
aws s3 ls | grep $BUCKET_NAME
마지막으로 ack-s3-controller를 삭제한다.
# helm uninstall
export SERVICE=s3
helm uninstall -n $ACK_SYSTEM_NAMESPACE ack-$SERVICE-controller
# ACK S3 Controller 관련 crd 삭제
kubectl delete -f ~/$SERVICE-chart/crds
# IRSA 삭제
eksctl delete iamserviceaccount --cluster myeks --name ack-$SERVICE-controller --namespace ack-system
# namespace 삭제 >> ACK 모든 실습 후 삭제
kubectl delete namespace $ACK_K8S_NAMESPACE
실습 - ACK를 이용한 EC2 제어
Step 1: ack-ec2-ontroller 설치
# 서비스명 변수 지정 및 helm 차트 다운로드
export SERVICE=ec2
export RELEASE_VERSION=$(curl -sL https://api.github.com/repos/aws-controllers-k8s/$SERVICE-controller/releases/latest | grep '"tag_name":' | cut -d'"' -f4 | cut -c 2-)
helm pull oci://public.ecr.aws/aws-controllers-k8s/$SERVICE-chart --version=$RELEASE_VERSION
tar xzvf $SERVICE-chart-$RELEASE_VERSION.tgz
# helm chart 확인
tree ~/$SERVICE-chart
# ACK EC2-Controller 설치
export ACK_SYSTEM_NAMESPACE=ack-system
export AWS_REGION=ap-northeast-2
helm install -n $ACK_SYSTEM_NAMESPACE ack-$SERVICE-controller --set aws.region="$AWS_REGION" ~/$SERVICE-chart
# 설치 확인
helm list --namespace $ACK_SYSTEM_NAMESPACE
kubectl -n $ACK_SYSTEM_NAMESPACE get pods -l "app.kubernetes.io/instance=ack-$SERVICE-controller"
kubectl get crd | grep $SERVICE
Step 2: EC2에 대한 IRSA 설정
# Create an iamserviceaccount - AWS IAM role bound to a Kubernetes service account
eksctl create iamserviceaccount \
--name ack-$SERVICE-controller \
--namespace $ACK_SYSTEM_NAMESPACE \
--cluster $CLUSTER_NAME \
--attach-policy-arn $(aws iam list-policies --query 'Policies[?PolicyName==`AmazonEC2FullAccess`].Arn' --output text) \
--override-existing-serviceaccounts --approve
# 확인 >> 웹 관리 콘솔에서 CloudFormation Stack >> IAM Role 확인
eksctl get iamserviceaccount --cluster $CLUSTER_NAME | grep ack
# Inspecting the newly created Kubernetes Service Account, we can see the role we want it to assume in our pod.
kubectl get sa -n $ACK_SYSTEM_NAMESPACE
kubectl describe sa ack-$SERVICE-controller -n $ACK_SYSTEM_NAMESPACE
# Restart ACK service controller deployment using the following commands.
kubectl -n $ACK_SYSTEM_NAMESPACE rollout restart deploy ack-$SERVICE-controller-$SERVICE-chart
IRSA 적용을 통해 생성된 Env, Volume도 확인해 본다.
# IRSA 적용으로 Env, Volume 추가 확인›
kubectl describe pod -n $ACK_SYSTEM_NAMESPACE -l k8s-app=$SERVICE-chart
Step 3: EC2 리소스 제어
VPC와 Subnet 리소스를 제어한다.
# [터미널1] 모니터링
while true; do aws ec2 describe-vpcs --query 'Vpcs[*].{VPCId:VpcId, CidrBlock:CidrBlock}' --output text; echo "-----"; sleep 1; done
# VPC 생성
cat <<EOF > vpc.yaml
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: VPC
metadata:
name: vpc-tutorial-test
spec:
cidrBlocks:
- 10.0.0.0/16
enableDNSSupport: true
enableDNSHostnames: true
EOF
kubectl apply -f vpc.yaml
vpc.ec2.services.k8s.aws/vpc-tutorial-test created
# VPC 생성 확인
kubectl get vpcs
kubectl describe vpcs
aws ec2 describe-vpcs --query 'Vpcs[*].{VPCId:VpcId, CidrBlock:CidrBlock}' --output text
# [터미널1] 모니터링
VPCID=$(kubectl get vpcs vpc-tutorial-test -o jsonpath={.status.vpcID})
while true; do aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPCID" --query 'Subnets[*].{SubnetId:SubnetId, CidrBlock:CidrBlock}' --output text; echo "-----"; sleep 1 ; done
# 서브넷 생성
VPCID=$(kubectl get vpcs vpc-tutorial-test -o jsonpath={.status.vpcID})
cat <<EOF > subnet.yaml
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: Subnet
metadata:
name: subnet-tutorial-test
spec:
cidrBlock: 10.0.0.0/20
vpcID: $VPCID
EOF
kubectl apply -f subnet.yaml
# 서브넷 생성 확인
kubectl get subnets
kubectl describe subnets
aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPCID" --query 'Subnets[*].{SubnetId:SubnetId, CidrBlock:CidrBlock}' --output text
Step 4: EC2 리소스 삭제
=# 리소스 삭제
kubectl delete -f subnet.yaml && kubectl delete -f vpc.yaml
실습 - ACK Workflow를 이용한 리소스 제어
VPC 내에 Public/Private Subnet을 각각 생성하고, NAT GW, IGW를 생성한 후 인스턴스를 배치하는 Workflow를 실습했다.
앞선 실습에서 사용하던 ack-ec2-controller를 계속 사용하기 때문에 설치 과정은 생략하도록 한다.
Step 1: ack-ec2-ontroller 설치 (생략)
Step 2: EC2에 대한 IRSA 설정 (생략)
Step 3: workflow 를 통해 리소스 제어
cat <<EOF > vpc-workflow.yaml
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: VPC
metadata:
name: tutorial-vpc
spec:
cidrBlocks:
- 10.0.0.0/16
enableDNSSupport: true
enableDNSHostnames: true
tags:
- key: name
value: vpc-tutorial
---
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: InternetGateway
metadata:
name: tutorial-igw
spec:
vpcRef:
from:
name: tutorial-vpc
---
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: NATGateway
metadata:
name: tutorial-natgateway1
spec:
subnetRef:
from:
name: tutorial-public-subnet1
allocationRef:
from:
name: tutorial-eip1
---
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: ElasticIPAddress
metadata:
name: tutorial-eip1
spec:
tags:
- key: name
value: eip-tutorial
---
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: RouteTable
metadata:
name: tutorial-public-route-table
spec:
vpcRef:
from:
name: tutorial-vpc
routes:
- destinationCIDRBlock: 0.0.0.0/0
gatewayRef:
from:
name: tutorial-igw
---
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: RouteTable
metadata:
name: tutorial-private-route-table-az1
spec:
vpcRef:
from:
name: tutorial-vpc
routes:
- destinationCIDRBlock: 0.0.0.0/0
natGatewayRef:
from:
name: tutorial-natgateway1
---
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: Subnet
metadata:
name: tutorial-public-subnet1
spec:
availabilityZone: ap-northeast-2a
cidrBlock: 10.0.0.0/20
mapPublicIPOnLaunch: true
vpcRef:
from:
name: tutorial-vpc
routeTableRefs:
- from:
name: tutorial-public-route-table
---
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: Subnet
metadata:
name: tutorial-private-subnet1
spec:
availabilityZone: ap-northeast-2a
cidrBlock: 10.0.128.0/20
vpcRef:
from:
name: tutorial-vpc
routeTableRefs:
- from:
name: tutorial-private-route-table-az1
---
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: SecurityGroup
metadata:
name: tutorial-security-group
spec:
description: "ack security group"
name: tutorial-sg
vpcRef:
from:
name: tutorial-vpc
ingressRules:
- ipProtocol: tcp
fromPort: 22
toPort: 22
ipRanges:
- cidrIP: "0.0.0.0/0"
description: "ingress"
EOF
생성된 yaml 파일을 통해 workflow를 배포한다.
# VPC 환경 생성
kubectl apply -f vpc-workflow.yaml
# [터미널1] NATGW 생성 완료 후 tutorial-private-route-table-az1 라우팅 테이블 ID가 확인되고 그후 tutorial-private-subnet1 서브넷ID가 확인됨 > 5분 정도 시간 소요
watch -d kubectl get routetables,subnet
# VPC 환경 생성 확인
kubectl describe vpcs
kubectl describe internetgateways
kubectl describe routetables
kubectl describe natgateways
kubectl describe elasticipaddresses
kubectl describe securitygroups
다음으로 Public Subnet에 EC2 인스턴스를 생성한다.
# public 서브넷 ID 확인
PUBSUB1=$(kubectl get subnets tutorial-public-subnet1 -o jsonpath={.status.subnetID})
echo $PUBSUB1
# 보안그룹 ID 확인
TSG=$(kubectl get securitygroups tutorial-security-group -o jsonpath={.status.id})
echo $TSG
# Amazon Linux 2 최신 AMI ID 확인
AL2AMI=$(aws ec2 describe-images --owners amazon --filters "Name=name,Values=amzn2-ami-hvm-2.0.*-x86_64-gp2" --query 'Images[0].ImageId' --output text)
echo $AL2AMI
# 각자 자신의 SSH 키페어 이름 변수 지정
MYKEYPAIR=<각자 자신의 SSH 키페어 이름>
MYKEYPAIR=kp-gasida
# 변수 확인 > 특히 서브넷 ID가 확인되었는지 꼭 확인하자!
echo $PUBSUB1 , $TSG , $AL2AMI , $MYKEYPAIR
# [터미널1] 모니터링
while true; do aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,PrivateIPAdd:PrivateIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output table; date ; sleep 1 ; done
# public 서브넷에 인스턴스 생성
cat <<EOF > tutorial-bastion-host.yaml
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: Instance
metadata:
name: tutorial-bastion-host
spec:
imageID: $AL2AMI # AL2 AMI ID - ap-northeast-2
instanceType: t3.medium
subnetID: $PUBSUB1
securityGroupIDs:
- $TSG
keyName: $MYKEYPAIR
tags:
- key: producer
value: ack
EOF
kubectl apply -f tutorial-bastion-host.yaml
# 인스턴스 생성 확인
kubectl get instance
kubectl describe instance
aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,PrivateIPAdd:PrivateIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output table
Public Subnet에 배포된 인스턴스에 접속해서 외부 통신을 시도하면 잘 안 되는 이유는 egress 설정이 되어 있지 않기 때문이다.
다음과 같이 8.8.8.8에 ping을 날려도 반응이 없다.
ssh -i <자신의 키페어파일> ec2-user@<public 서브넷에 인스턴스 퍼블릭IP>
------
# public 서브넷에 인스턴스 접속 후 외부 인터넷 통신 여부 확인
ping -c 2 8.8.8.8
외부로 통신할 수 있도록 egress 규칙을 추가한다.
cat <<EOF > modify-sg.yaml
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: SecurityGroup
metadata:
name: tutorial-security-group
spec:
description: "ack security group"
name: tutorial-sg
vpcRef:
from:
name: tutorial-vpc
ingressRules:
- ipProtocol: tcp
fromPort: 22
toPort: 22
ipRanges:
- cidrIP: "0.0.0.0/0"
description: "ingress"
egressRules:
- ipProtocol: '-1'
ipRanges:
- cidrIP: "0.0.0.0/0"
description: "egress"
EOF
kubectl apply -f modify-sg.yaml
# 변경 확인 >> 보안그룹에 아웃바운드 규칙 확인
kubectl logs -n $ACK_SYSTEM_NAMESPACE -l k8s-app=ec2-chart -f
마지막으로 인스턴스에 접속하여 통신이 잘 되는 것을 확인했다.
ssh -i <자신의 키페어파일> ec2-user@<public 서브넷에 인스턴스 퍼블릭IP>
# public 서브넷에 인스턴스 접속 후 외부 인터넷 통신 여부 확인
ping -c 10 8.8.8.8
curl ipinfo.io/ip ; echo
exit
통신 IP를 확인해보면 인스턴스의 IP를 통해 외부와 통신하는 것을 알 수 있다.
다음으로 프라이빗 서브넷 역시 인스턴스를 생성한다.
# private 서브넷 ID 확인 >> NATGW 생성 완료 후 RT/SubnetID가 확인되어 다소 시간 필요함
PRISUB1=$(kubectl get subnets tutorial-private-subnet1 -o jsonpath={.status.subnetID})
echo $PRISUB1
# 변수 확인 > 특히 private 서브넷 ID가 확인되었는지 꼭 확인하자!
echo $PRISUB1 , $TSG , $AL2AMI , $MYKEYPAIR
# private 서브넷에 인스턴스 생성
cat <<EOF > tutorial-instance-private.yaml
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: Instance
metadata:
name: tutorial-instance-private
spec:
imageID: $AL2AMI # AL2 AMI ID - ap-northeast-2
instanceType: t3.medium
subnetID: $PRISUB1
securityGroupIDs:
- $TSG
keyName: $MYKEYPAIR
tags:
- key: producer
value: ack
EOF
kubectl apply -f tutorial-instance-private.yaml
# 인스턴스 생성 확인
kubectl get instance
kubectl describe instance
aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,PrivateIPAdd:PrivateIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output table
Private Subnet에 배포된 인스턴스에 원격 접속하기 위해 SSH 터널링 작업을 진행한다.
ssh -i <자신의 키페어파일> -L <자신의 임의 로컬 포트>:<private 서브넷의 인스턴스의 private ip 주소>:22 ec2-user@<public 서브넷에 인스턴스 퍼블릭IP> -v
ssh -i ~/.ssh/kp-gasida.pem -L 7777:10.0.129.196:22 ec2-user@3.34.96.12 -v
---
접속 후 그냥 두기
---
다음으로 로컬 포트로 SSH 접속시 Private Subnet에 접속되는 것을 확인할 수 있다.
ssh -i <자신의 키페어파일> -p <자신의 임의 로컬 포트> ec2-user@localhost
ssh -i ~/.ssh/kp-gasida.pem -p 7777 ec2-user@localhost
---
# IP 및 네트워크 정보 확인
ip -c addr
sudo ss -tnp
ping -c 2 8.8.8.8
curl ipinfo.io/ip ; echo # 출력되는 공인IP는 무엇인가?
exit
---
Private GW에서 외부 통신할때는 NAT GW를 통하는 것을 확인할 수 있다.
Step 4: workflow 를 통해 생성한 리소스 삭제
kubectl delete -f tutorial-bastion-host.yaml && kubectl delete -f tutorial-instance-private.yaml
kubectl delete -f vpc-workflow.yaml # vpc 관련 모든 리소스들 삭제에는 다소 시간이 소요됨
실습 - ACK를 이용한 RDS 제어
RDS (Relational Database Service)는 관리형 관계형 데이터베이스 서비스로써, 편리하게 관계형 데이터베이스를 프로비저닝 하여 운영할 수 있다.
RDS는 여러가지 종류의 DB를 엔진의 개념을 통해 제공한다.
RDS를 생성하는 yaml에서 spec.engine 파라미터를 이용하여 사용할 DB의 종류를 정할 수 있으며, 실습에서는 Maria DB 엔진을 사용했다.
Step 1: ack-rds-ontroller 설치
# 서비스명 변수 지정 및 helm 차트 다운로드
export SERVICE=rds
export RELEASE_VERSION=$(curl -sL https://api.github.com/repos/aws-controllers-k8s/$SERVICE-controller/releases/latest | grep '"tag_name":' | cut -d'"' -f4 | cut -c 2-)
helm pull oci://public.ecr.aws/aws-controllers-k8s/$SERVICE-chart --version=$RELEASE_VERSION
tar xzvf $SERVICE-chart-$RELEASE_VERSION.tgz
# helm chart 확인
tree ~/$SERVICE-chart
# ACK EC2-Controller 설치
export ACK_SYSTEM_NAMESPACE=ack-system
export AWS_REGION=ap-northeast-2
helm install -n $ACK_SYSTEM_NAMESPACE ack-$SERVICE-controller --set aws.region="$AWS_REGION" ~/$SERVICE-chart
# 설치 확인
helm list --namespace $ACK_SYSTEM_NAMESPACE
kubectl -n $ACK_SYSTEM_NAMESPACE get pods -l "app.kubernetes.io/instance=ack-$SERVICE-controller"
kubectl get crd | grep $SERVICE
Step 2: RDS에 대한 IRSA 설정
# Create an iamserviceaccount - AWS IAM role bound to a Kubernetes service account
eksctl create iamserviceaccount \
--name ack-$SERVICE-controller \
--namespace $ACK_SYSTEM_NAMESPACE \
--cluster $CLUSTER_NAME \
--attach-policy-arn $(aws iam list-policies --query 'Policies[?PolicyName==`AmazonRDSFullAccess`].Arn' --output text) \
--override-existing-serviceaccounts --approve
# 확인 >> 웹 관리 콘솔에서 CloudFormation Stack >> IAM Role 확인
eksctl get iamserviceaccount --cluster $CLUSTER_NAME
# Inspecting the newly created Kubernetes Service Account, we can see the role we want it to assume in our pod.
kubectl get sa -n $ACK_SYSTEM_NAMESPACE
kubectl describe sa ack-$SERVICE-controller -n $ACK_SYSTEM_NAMESPACE
# Restart ACK service controller deployment using the following commands.
kubectl -n $ACK_SYSTEM_NAMESPACE rollout restart deploy ack-$SERVICE-controller-$SERVICE-chart
# IRSA 적용으로 Env, Volume 추가 확인
kubectl describe pod -n $ACK_SYSTEM_NAMESPACE -l k8s-app=$SERVICE-chart
Step 3: RDS 리소스 제어 및 테스트
# DB 암호를 위한 secret 생성
RDS_INSTANCE_NAME="<your instance name>"
RDS_INSTANCE_PASSWORD="<your instance password>"
kubectl create secret generic "${RDS_INSTANCE_NAME}-password" --from-literal=password="${RDS_INSTANCE_PASSWORD}"
# 확인
kubectl get secret $RDS_INSTANCE_NAME-password
# [터미널1] 모니터링
RDS_INSTANCE_NAME=myrds
watch -d "kubectl describe dbinstance "${RDS_INSTANCE_NAME}" | grep 'Db Instance Status'"
# RDS 배포 생성 : 15분 이내 시간 소요 >> 보안그룹, 서브넷 등 필요한 옵션들은 추가해서 설정해보자!
# 넵!! (보안그룹, 서브넷)
aws rds create-db-subnet-group \
--db-subnet-group-name my-subnet-group \
--db-subnet-group-description "My Subnet Group" \
--subnet-ids subnet-06572d9d66c917586 subnet-0b59e6c453c7afb73 subnet-027fba416df5acf5c
cat <<EOF > rds-mariadb.yaml
apiVersion: rds.services.k8s.aws/v1alpha1
kind: DBInstance
metadata:
name: "${RDS_INSTANCE_NAME}"
spec:
allocatedStorage: 20
dbInstanceClass: db.t4g.micro
dbInstanceIdentifier: "${RDS_INSTANCE_NAME}"
engine: mariadb
engineVersion: "10.6"
masterUsername: "admin"
masterUserPassword:
namespace: default
name: "${RDS_INSTANCE_NAME}-password"
key: password
vpcSecurityGroupIDs:
- "sg-054825ef98d24b68b" #기존 클러스터 내부 통신용 SecurityGroupID 사용
dbSubnetGroupName: "my-subnet-group"
EOF
kubectl apply -f rds-mariadb.yaml
# 생성 확인
kubectl get dbinstances ${RDS_INSTANCE_NAME}
kubectl describe dbinstance "${RDS_INSTANCE_NAME}"
aws rds describe-db-instances --db-instance-identifier $RDS_INSTANCE_NAME | jq
# 생성 완료 대기 : for 지정 상태가 완료되면 정상 종료됨
kubectl wait dbinstances ${RDS_INSTANCE_NAME} --for=condition=ACK.ResourceSynced --timeout=15m
dbinstance.rds.services.k8s.aws/myrds condition met
마지막으로 RDS가 정상적으로 동작하는지 확인하기위해 Field exporter를 생성한다.
Field exporter는 RDS의 설정정보를 k8s ConfigMap 형태로 출력하는 역할을 한다.
RDS_INSTANCE_CONN_CM="${RDS_INSTANCE_NAME}-conn-cm"
cat <<EOF > rds-field-exports.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
name: ${RDS_INSTANCE_CONN_CM}
data: {}
---
apiVersion: services.k8s.aws/v1alpha1
kind: FieldExport
metadata:
name: ${RDS_INSTANCE_NAME}-host
spec:
to:
name: ${RDS_INSTANCE_CONN_CM}
kind: configmap
from:
path: ".status.endpoint.address"
resource:
group: rds.services.k8s.aws
kind: DBInstance
name: ${RDS_INSTANCE_NAME}
---
apiVersion: services.k8s.aws/v1alpha1
kind: FieldExport
metadata:
name: ${RDS_INSTANCE_NAME}-port
spec:
to:
name: ${RDS_INSTANCE_CONN_CM}
kind: configmap
from:
path: ".status.endpoint.port"
resource:
group: rds.services.k8s.aws
kind: DBInstance
name: ${RDS_INSTANCE_NAME}
---
apiVersion: services.k8s.aws/v1alpha1
kind: FieldExport
metadata:
name: ${RDS_INSTANCE_NAME}-user
spec:
to:
name: ${RDS_INSTANCE_CONN_CM}
kind: configmap
from:
path: ".spec.masterUsername"
resource:
group: rds.services.k8s.aws
kind: DBInstance
name: ${RDS_INSTANCE_NAME}
EOF
kubectl apply -f rds-field-exports.yaml
다음으로 field들이 정상적으로 출력되었는지 확인해본다.
# 상태 정보 확인 : address 와 port 정보
kubectl get dbinstances myrds -o jsonpath={.status.endpoint} | jq
# 상태 정보 확인 : masterUsername 확인
kubectl get dbinstances myrds -o jsonpath={.spec.masterUsername} ; echo
# 컨피그맵 확인
kubectl get cm myrds-conn-cm -o yaml | kubectl neat | yh
apiVersion: v1
data:
default.myrds-host: myrds.cb79jlim4dyq.ap-northeast-2.rds.amazonaws.com
default.myrds-port: "3306"
default.myrds-user: admin
kind: ConfigMap
metadata:
name: myrds-conn-cm
namespace: default
# fieldexport 정보 확인
kubectl get crd | grep fieldexport
kubectl get fieldexport
kubectl get fieldexport myrds-host -o yaml | k neat | yh
마지막으로 RDS를 사용하는 Pod에 접속하고, Pod에 RDS 접속정보가 정상적으로 환경변수에 등록되었는지 확인해 본다.
APP_NAMESPACE=default
cat <<EOF > rds-pods.yaml
apiVersion: v1
kind: Pod
metadata:
name: app
namespace: ${APP_NAMESPACE}
spec:
containers:
- image: busybox
name: myapp
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
env:
- name: DBHOST
valueFrom:
configMapKeyRef:
name: ${RDS_INSTANCE_CONN_CM}
key: "${APP_NAMESPACE}.${RDS_INSTANCE_NAME}-host"
- name: DBPORT
valueFrom:
configMapKeyRef:
name: ${RDS_INSTANCE_CONN_CM}
key: "${APP_NAMESPACE}.${RDS_INSTANCE_NAME}-port"
- name: DBUSER
valueFrom:
configMapKeyRef:
name: ${RDS_INSTANCE_CONN_CM}
key: "${APP_NAMESPACE}.${RDS_INSTANCE_NAME}-user"
- name: DBPASSWORD
valueFrom:
secretKeyRef:
name: "${RDS_INSTANCE_NAME}-password"
key: password
EOF
kubectl apply -f rds-pods.yaml
# 생성 확인
kubectl get pod app
# 파드의 환경 변수 확인
kubectl exec -it app -- env | grep DB
다음과 같이 configMap 정보를 Pod가 잘 읽어서 환경변수로 가지고 있는 것을 확인할 수 있다.
하지만 RDS의 정보를 업데이트할 때 Pod가 자동적으로 동기화되지 않는 문제점이 있기 때문에 rollout 방식으로 env를 변경하는 식의 방법을 사용해야 한다.
# [터미널]
watch -d "kubectl get dbinstance; echo; kubectl get cm myrds-conn-cm -o yaml | kubectl neat"
# 아래 처럼 RDS 에서 직접 변경 할 경우 rds controller 를 별도 추적을 하지 않아서, k8s 상태와 aws 상태 정보가 깨져버럼
# DB 식별자 변경 : studyend
kubectl patch dbinstance myrds --type=merge -p '{"spec":{"dbInstanceIdentifier":"studyend"}}'
# 확인
kubectl get dbinstance myrds
kubectl describe dbinstance myrds
# 상태 정보 확인 : address 변경 확인!
kubectl get dbinstances myrds -o jsonpath={.status.endpoint} | jq
# 파드의 환경 변수 확인 >> 파드의 경우 환경 변수 env로 정보를 주입했기 때문에 변경된 정보를 확인 할 수 없다
kubectl exec -it app -- env | grep DB
DBHOST=myrds.cb79jlim4dyq.ap-northeast-2.rds.amazonaws.com
DBPORT=3306
DBUSER=admin
DBPASSWORD=qwe12345
# 파드 삭제 후 재생성 후 확인
kubectl delete pod app && kubectl apply -f rds-pods.yaml
# 파드의 환경 변수 확인 >> 변경 정보 확인!
# 즉 deployments, daemonsets, statefulsets 의 경우 rollout 으로 env 변경 적용을 할 수 는 있겠다!
RDS 환경설정을 변경시키면 새로운 DB가 프러비저닝 된다.
위와 같이 configMap 정보가 업데이트되어도, Pod의 정보는 업데이트되지 않는다. 그래서 Pod를 지웠다가 다시 실행시키면 정상적으로 configMap 정보를 통해 env가 세팅되어 있음을 확인할 수 있다.
Step 4: RDS 리소스 삭제
마지막으로 실습자원을 삭제한다.
# 파드 삭제
kubectl delete pod app
# RDS 삭제
kubectl delete -f rds-mariadb.yaml
Flux
Flux는 Kubernetes 클러스터의 애플리케이션 배포 및 구성 관리를 자동화하기 위한 오픈 소스 프로젝트다. Flux는 GitOps 원칙을 기반으로 하여 애플리케이션의 배포, 업데이트, 롤백 등을 Git 리포지토리에서의 상태 변경을 통해 관리한다.
참고 - 링크
이전 PKOS 스터디에서 배웠던 Argo CD와 비슷한 역할을 하지만, 다음과 같은 차이가 있다.
- 설계 철학
- Flux: Flux는 GitOps의 개념을 중심으로 설계되었습니다. GitOps는 Git 리포지토리를 단일 소스로 사용하여 클러스터 상태를 관리하는 방식을 의미한다.
- Argo CD: Argo CD는 Declarative Continuous Deployment를 목표로 하는 프로젝트다. Argo CD는 GitOps 원칙을 따르지만, Flux와 달리 다양한 배포 전략과 파이프라인 기능을 제공한다.
- 배포 전략
- Flux: Flux는 기본적으로 지속적인 배포를 지원합니다. Git 리포지토리의 변경 사항을 감지하고, 새로운 커밋에 따라 애플리케이션을 자동으로 업데이트한다.
- Argo CD: Argo CD는 롤아웃 전략과 히스토리 관리를 통해 배포 전략을 더 세밀하게 제어할 수 있습니다. 롤아웃 전략에는 블루-그린, 카나리아, 점진적 롤아웃 등이 포함한다.
- 사용자 인터페이스
- Flux: Flux는 주로 CLI(Command-Line Interface)를 통해 사용된다. Flux CLI를 사용하여 Flux의 구성, 배포 상태, 롤백 등을 관리할 수 있다.
- Argo CD: Argo CD는 사용자 친화적인 웹 기반 UI를 제공한다. 이 UI를 통해 애플리케이션의 배포 상태, 롤백, 세부 설정 등을 시각적으로 관리할 수 있다.
- 확장성
- Flux: Flux는 단일 군집(Kubernetes 클러스터)에서 작동하는 데 초점을 맞추고 있다. Flux는 단일 클러스터에서 여러 애플리케이션을 관리하는 데 유용하다.
- Argo CD: Argo CD는 다중 군집(Multi-cluster) 환경에서의 애플리케이션 배포를 관리하는 데 더욱 특화되어 있다. 여러 개의 Kubernetes 클러스터에서 배포를 조정하고 관리할 수 있다.
실습 - Flux를 사용하여 kustomize 기반 리소스 배포
Flux를 사용하기 위해 flux를 설치하고, github repository를 연결한다.
# Flux CLI 설치
curl -s https://fluxcd.io/install.sh | sudo bash
. <(flux completion bash)
# 버전 확인
flux --version
flux version 2.0.0-rc.5
# 자신의 Github 토큰과 유저이름 변수 지정
export GITHUB_TOKEN=<your-token>
export GITHUB_USER=<your-username>
# Bootstrap
## Creates a git repository fleet-infra on your GitHub account.
## Adds Flux component manifests to the repository.
## Deploys Flux Components to your Kubernetes Cluster.
## Configures Flux components to track the path /clusters/my-cluster/ in the repository.
flux bootstrap github \
--owner=$GITHUB_USER \
--repository=fleet-infra \
--branch=main \
--path=./clusters/my-cluster \
--personal
# 설치 확인
kubectl get pods -n flux-system
kubectl get-all -n flux-system
kubectl get crd | grep fluxc
kubectl get gitrepository -n flux-system
Repository를 연결하면 다음과 같이 Github에 private 저장소가 생성된다.
다음으로는 gitops 도구 중 하나인 flux 대시보드를 설치한다. (초기 계정 접속 정보는 admin / password이다.)
# gitops 도구 설치
curl --silent --location "https://github.com/weaveworks/weave-gitops/releases/download/v0.24.0/gitops-$(uname)-$(uname -m).tar.gz" | tar xz -C /tmp
sudo mv /tmp/gitops /usr/local/bin
gitops version
# flux 대시보드 설치
PASSWORD="password"
gitops create dashboard ww-gitops --password=$PASSWORD
# 확인
flux -n flux-system get helmrelease
kubectl -n flux-system get pod,svc
설치 후에 외부에서 접속이 가능하도록 Ingress 설정을 해준다.
CERT_ARN=`aws acm list-certificates --query 'CertificateSummaryList[].CertificateArn[]' --output text`
echo $CERT_ARN
# Ingress 설정
cat <<EOT > gitops-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gitops-ingress
annotations:
alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
alb.ingress.kubernetes.io/group.name: study
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/ssl-redirect: "443"
alb.ingress.kubernetes.io/success-codes: 200-399
alb.ingress.kubernetes.io/target-type: ip
spec:
ingressClassName: alb
rules:
- host: gitops.$MyDomain
http:
paths:
- backend:
service:
name: ww-gitops-weave-gitops
port:
number: 9001
path: /
pathType: Prefix
EOT
kubectl apply -f gitops-ingress.yaml -n flux-system
# 배포 확인
kubectl get ingress -n flux-system
# GitOps 접속 정보 확인 >> 웹 접속 후 정보 확인
echo -e "GitOps Web https://gitops.$MyDomain"
다음으로 kustomize nginx 샘플 애플리케이션의 repository를 등록한다. 실습 리포지토리는 스터디 멤버이신 악분(최성욱)님의 리포지토리를 이용한다.
# 소스 생성 : 유형 - git, helm, oci, bucket
# flux create source {소스 유형}
# 악분(최성욱)님이 준비한 repo로 git 소스 생성
GITURL="https://github.com/sungwook-practice/fluxcd-test.git"
flux create source git nginx-example1 --url=$GITURL --branch=main --interval=30s
# 소스 확인
flux get sources git
kubectl -n flux-system get gitrepositories
이제 kustomization을 통해 flux 애플리케이션을 생성한다.
# [터미널] 모니터링
watch -d kubectl get pod,svc nginx-example1
# flux 애플리케이션 생성 : nginx-example1
flux create kustomization nginx-example1 --target-namespace=default --interval=1m --source=nginx-example1 --path="./nginx" --health-check-timeout=2m
# 확인
kubectl get pod,svc nginx-example1
kubectl get kustomizations -n flux-system
flux get kustomizations
대시보드에 접속하면 다음과 같은 구성을 확인할 수 있다.
마지막으로 애플리케이션을 삭제한다.
# [터미널] 모니터링
watch -d kubectl get pod,svc nginx-example1
# flux 애플리케이션 삭제 >> 파드와 서비스는? flux 애플리케이션 생성 시 --prune 옵션 false(default 값)
flux delete kustomization nginx-example1
flux get kustomizations
kubectl get pod,svc nginx-example1
# flux 애플리케이션 다시 생성 : --prune 옵션 true
flux create kustomization nginx-example1 \
--target-namespace=default \
--prune=true \
--interval=1m \
--source=nginx-example1 \
--path="./nginx" \
--health-check-timeout=2m
# 확인
flux get kustomizations
kubectl get pod,svc nginx-example1
# flux 애플리케이션 삭제 >> 파드와 서비스는?
flux delete kustomization nginx-example1
flux get kustomizations
kubectl get pod,svc nginx-example1
# flux 소스 삭제
flux delete source git nginx-example1
# 소스 확인
flux get sources git
kubectl -n flux-system get gitrepositories
실습 - Docs 샘플 실습
공식 예제 - 참고 링크
Repository에 업데이트된 정보가 Flux를 통해 대시보드에 업데이트되는 과정을 확인하기 위해 아래와 같이 Flux를 설치한다.
# Clone the git repository : 자신의 Github 의 Username, Token 입력
git clone https://github.com/$GITHUB_USER/fleet-infra
Username for 'https://github.com': <자신의 Github 의 Username>
Password for 'https://gasida@github.com': <자신의 Github의 Token>
# 폴더 이동
cd fleet-infra
tree
# GitRepository yaml 파일 생성
flux create source git podinfo \
--url=https://github.com/stefanprodan/podinfo \
--branch=master \
--interval=30s \
--export > ./clusters/my-cluster/podinfo-source.yaml
# GitRepository yaml 파일 확인
cat ./clusters/my-cluster/podinfo-source.yaml | yh
# Commit and push the podinfo-source.yaml file to the fleet-infra repository >> Github 확인
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git add -A && git commit -m "Add podinfo GitRepository"
git push
Username for 'https://github.com': <자신의 Github 의 Username>
Password for 'https://gasida@github.com': <자신의 Github의 Token>
# 소스 확인
flux get sources git
kubectl -n flux-system get gitrepositories
flux 설치가 끝나면 다음과 같이 Repository가 자동으로 생성된다.
다음으로는 kustomize를 이용하여 Application을 배포한다.
# [터미널]
kubectl scale deployment podinfo --replicas 3
대시보드에서 증가한 replica를 확인할 수 있다.
마지막으로 실습 자원을 삭제한다.
flux delete kustomization podinfo
flux delete source git podinfo
#
flux uninstall --namespace=flux-system
마치며
이번 포스팅에서는 AWS에서 배운 Automation에 대해 배웠다. 사실 지난주에 스터디했던 인증/인가 개념보다 어려운 것은 없었지만, 과제 중에 보안그룹과 서브넷등 필요한 옵션을 추가하는 것은 쉽지 않았다.
뭔가 가시 다님께서 적어놓으신 게 "이 정도는 할 수 있죠?" 느낌인 것 같아서, 질문하지 않고 해결하기 위해 고민을 많이 했었던 것 같다. 그리고 상황을 해결해 나가면서 클라우드 인프라에 대한 이해를 좀 더 할 수 있는 계기가 되었다.
그리고 Automation 세미나를 들으면서 느낀 생각은.. K8s를 다루는 엔지니어 다수로 구성된 조직에서는 ACK를 도입하는게 편의성 측면에서는 도움이 될 수 있겠으나, 그렇지 않다면 굳이 써야할 이유가 있을까 싶은 생각이 든다. 사실 이보다는 테라폼 같은 것을 사용하는 것이 더 깔끔하고 좋아보인다. AWS 리소스를 사용하는 편의 툴이나 방식이 중요하다기보단, 리소스의 본질에 대해 이해하고 무엇이 필요한지를 그때 그때 아는것이 중요하다고 생각되었다.
이번 과제를 끝으로 AWES 세미나 스터디도 마무리가 될 것 같다.(통과해야지만 ㅠㅠ) 5~6월 달려오면서 여러 가지 일정들이 많았음에도 불구하고 포기하지 않고 끝까지 버텨낸 필자가 자랑스럽다. (스터디에 참여한 다른 멤버들 역시 비슷한 느낌이지 않을까?)
함께 스터디를 진행했던 동료 분들도 정말 대단하다고 생각한다. 힘든 순간들을 이겨내면서 스터디 참여 전보다 AWS와 k8s환경에 대한 이해도가 훨씬 올라간 것 같다.
실제 프로젝트에 적용하기 위해 스터디 종료 이후에도 정리한 내용들을 열심히 복기할 것이다 :)
'클라우드 컴퓨팅 & NoSQL > [AEWS] Amazon EKS 워크숍 스터디' 카테고리의 다른 글
[6주차] AEWS Amazon EKS 워크숍 스터디 (23.05.28) (3) | 2023.06.02 |
---|---|
[5주차] AEWS Amazon EKS 워크숍 스터디 (23.05.21) (0) | 2023.05.27 |
[4주차] AEWS Amazon EKS 워크숍 스터디 (23.05.14) (0) | 2023.05.20 |
[3주차] AEWS Amazon EKS 워크숍 스터디 (23.05.07) (2) | 2023.05.14 |
[2주차] AEWS Amazon EKS 워크숍 스터디 (23.04.30) (0) | 2023.05.07 |