AEWS 스터디에서는 AWS의 관리형 Kubernetes인 Elastic Kubernetes의 다양한 기능들을 실습해보면서 익혀본다. 이 글은 스터디를 참여하면서 학습한 내용을 정리하는 연재 글이다. 스터디 진도에 맞춰 글을 작성한다.
이 글에서는 EKS Automation - ACK 에 대해서 알아본다.
1. AWS Controller for Kubernetes (ACK)
AWS Controller for Kubernetes (ACK) : aws 서비스 리소스를 k8s 에서 직접 정의하고 사용 할 수 있다.
EKS 어플리케이션들이 AWS 서비스를 쿠버네티스 네이티브하게 정리해서 사용할 수 있도록 yaml 로 생성할 수 있다. 예를 들면 s3 이면 각 서비스별로 ack-s3-controller가 있다.
ACK 가 지원하는 AWS 서비스
GA가 된 서비스들이 얼마나 있는지 가 중요할 텐데, 꽤 많은 서비스들이 GA 되어 있고 EKS도 되어 있다. 현재 17개 서비스는 GA General Availability (정식 출시) 되었고, 10개는 Preview (평가판) 상태이다.
- GA 서비스 : ApiGatewayV2, CloudTrail, DynamoDB, EC2, ECR, EKS, IAM, KMS, Lambda, MemoryDB, RDS, S3, SageMaker…
- Preview 서비스 : ACM, ElastiCache, EventBridge, MQ, Route 53, SNS, SQS…
Permissions
권한은 IRSA 방식으로 진행된다. 저번 실습했던 블로그 글 [AWES] EKS Security - IRSA 에서 확인 해볼 수 있다.
- IRSA role for ACK Service Controller: k8s api 와 aws api 의 2개의 RBAC 시스템 확인, 각 서비스 컨트롤러 파드는 AWS 서비스 권한 필요
1-1. 실습: S3
[ACK S3 Controller 설치 with Helm]
aws-리소스-controllers 라고 이름이 붙여지는 AWS Controllers for Kubernetes (ACK) 는 퍼블릭 저장소 (https://gallery.ecr.aws/aws-controllers-k8s )에 있다.
다운받아 helm chart 확인하고, ACK S3 Controller 설치하면 컨트롤러, crd가 확인 된다. 챙겨줘야할 것은 SA가 해당 AWS 리소스의 최대 접근 권한이 필요하다는 점으로 이 권한으로 IRSA를 세팅해준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
# 서비스명 변수 지정
export SERVICE=s3
# helm 차트 다운로드
#aws ecr-public get-login-password --region us-east-1 | helm registry login --username AWS --password-stdin public.ecr.aws
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
buckets.s3.services.k8s.aws 2022-04-24T13:24:00Z
kubectl get all -n ack-system
kubectl get-all -n ack-system
kubectl describe sa -n ack-system ack-s3-controller
|
cs |
[IRSA 설정 : 권장 정책 AmazonS3FullAccess]
IRSA를 세팅하기 위해 eksctl create iamserviceaccount를 사용한다. 권장 권한 정책은 서비스별로 정해져있는데 full access로 되어 있다. SA확인 하면 기존에 SA가 있어도 override로 적용하면 덮어쓴다. 이렇게 생성 후에 rollout으로 컨트롤러를 재시작한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
# 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
# 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
...
|
cs |
그럼 s3를 생성해보자!
[S3 버킷 생성, 업데이트, 삭제]
버킷은 글로벌리 유일해야해하기에 실습에서는 이름을 AWS_ACCOUNT_ID로 생성한다. 생성하고 aws cli로 확인 해보면 생성되어 있는 것이 확인 된다. yaml 형식으로 배포가 가능한 것을 확인 할 수 있다.
업데이트도 가능한데, 버킷에 tag를 포함해서 override 해본다.
태그가 myTagKey: myTagValue로 추가된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
# [터미널1] 모니터링
watch -d aws s3 ls
# S3 버킷 생성을 위한 설정 파일 생성
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)
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
# S3 버킷 업데이트 확인
kubectl describe bucket/$BUCKET_NAME | grep Spec: -A5
Spec:
Name: my-ack-s3-bucket-<my account id>
Tagging:
Tag Set:
Key: myTagKey
Value: myTagValue
# S3 버킷 삭제
kubectl delete -f bucket.yaml
# verify the bucket no longer exists
kubectl get bucket/$BUCKET_NAME
aws s3 ls | grep $BUCKET_NAME
|
cs |
[ACK S3 Controller 삭제]
삭제 시에도 AWS 방식이 아닌 쿠버네티스 방식으로 kubectl delete -f bucket.yaml로 삭제 가능하다 .
1
2
3
4
5
6
7
8
9
10
11
12
|
# 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
|
cs |
1-2. 실습: EC2 & VPC
s3와 동일한 과정으로 진행한다. iamserviceaccount 명령어로 IRSA진행하고 컨트롤러를 restart한다.
[ACK EC2-Controller 설치 with Helm]
ACK EC2-Controller 설치하면 EC2를 배포하기 위해서 필요한 기본적인 모든 crd가 통합으로 배포된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
# 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
# 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
...
|
cs |
[VPC, Subnet 생성 및 삭제]
10.0.0.0/16 으로 VPC 생성하고, 서브넷을 cidrBlock: 10.0.0.0/20으로 생성해본다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
# [터미널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
# 리소스 삭제
kubectl delete -f subnet.yaml && kubectl delete -f vpc.yaml
|
cs |
[Create a VPC Workflow : VPC, Subnet, SG, RT, EIP, IGW, NATGW, Instance 생성]
조금 복잡한 네트워크 환경을 구성해본다. 코드 내용을 확인 해보면 다음과 같다.
VPC생성, 후에 인터넷 게이트 웨이, NAT 에서 refVPC 참조해서 가져온다. 그리고 보안 그룹이 들어가 있다.
[vpc-workflow.yaml 파일 생성 : 아래 코드블록 내용 복붙할 것!]
vpc-workflow.yaml
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
Private 서브넷을 확인 해보면 의존성에 의해 NAT GW 등의 리소스 생성을 기다린다. 상대적으로 리소스 확인 이 느릴 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
# 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
# 배포 도중 2개의 서브넷 상태 정보 비교 해보자
kubectl describe subnets
...
Status:
Conditions:
Last Transition Time: 2023-06-04T02:15:25Z
Message: Reference resolution failed
Reason: the referenced resource is not synced yet. resource:RouteTable, namespace:default, name:tutorial-private-route-table-az1
Status: Unknown
Type: ACK.ReferencesResolved
...
Status:
Ack Resource Metadata:
Arn: arn:aws:ec2:ap-northeast-2:911283464785:subnet/subnet-0f5ae09e5d680030a
Owner Account ID: 911283464785
Region: ap-northeast-2
Available IP Address Count: 4091
Conditions:
Last Transition Time: 2023-06-04T02:14:45Z
Status: True
Type: ACK.ReferencesResolved
Last Transition Time: 2023-06-04T02:14:45Z
Message: Resource synced successfully
Reason:
Status: True
Type: ACK.ResourceSynced
...
|
cs |
Controller는 Desired State 와 current State를 비교하여 모니터링 한다.
[1. 퍼블릭 서브넷에 인스턴스 생성]
이렇게 생성한 네트워크 환경에 인스턴스를 배포해본다. 인스턴스를 확인 하면 key, 보안그룹, 이미지, 서브넷을 확인 할 수 있다. 이렇게 쿠버네티스 플랫폼 엔지니어가 인프라를 생성, 관리 할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
# 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=ygpark
# 변수 확인 > 특히 서브넷 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
|
cs |
[public 서브넷에 인스턴스 접속]
이제 인스턴스에 퍼블릭 서브넷으로 핑을 날려보자. 하지만 핑이 되지 않는다..!
1
2
3
4
5
6
|
ssh -i <자신의 키페어파일> ec2-user@<public 서브넷에 인스턴스 퍼블릭IP>
------
# public 서브넷에 인스턴스 접속 후 외부 인터넷 통신 여부 확인
ping -c 2 8.8.8.8
exit
------
|
cs |
[보안 그룹 정책 수정 : egress 규칙 추가]
핑을 해보면 되지 않는데 기본 egressRule이 deny이기 때문이다. 규칙을 모든 ip로 추가해주면 핑이 확인 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
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
|
cs |
[public 서브넷에 인스턴스 접속 후 외부 인터넷 통신 확인]
1
2
3
4
5
6
7
|
ssh -i <자신의 키페어파일> ec2-user@<public 서브넷에 인스턴스 퍼블릭IP>
------
# public 서브넷에 인스턴스 접속 후 외부 인터넷 통신 여부 확인
ping -c 10 8.8.8.8
curl ipinfo.io/ip ; echo # 출력되는 공인IP는 무엇인가?
exit
------
|
cs |
[2. 프라이빗 서브넷에 인스턴스 생성]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
# 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
|
cs |
프라이빗 서브넷 ID를 확인 해보면 NAT GW, 라우팅 테이블 생성과 attach 되어 가장 나중에 ID가 확인 된다. 이 서브넷에 인스턴스를 생성한다. 인스턴스를 확인 해보면 프라이빗 ip만 확인 되고 퍼블릭은 확인 안된다.
[public 서브넷에 인스턴스에 SSH 터널링 설정]
1
2
3
4
5
6
|
ssh -i <자신의 키페어파일> -L <자신의 임의 로컬 포트>:<private 서브넷의 인스턴스의 private ip 주소>:22 ec2-user@<public 서브넷에 인스턴스 퍼블릭IP> -v
ssh -i ~/.ssh/ygpark.pem -L 9999:10.0.129.196:22 ec2-user@3.34.96.12 -v
# 세 변수를 변경해서 명령어
---
접속 후 그냥 두기
---
|
cs |
[자신의 임의 로컬 포트로 SSH 접속 시, private 서브넷에 인스턴스 접속됨]
1
2
3
4
5
6
7
8
9
10
|
ssh -i <자신의 키페어파일> -p <자신의 임의 로컬 포트> ec2-user@localhost
ssh -i ~/.ssh/ygpark.pem -p 9999 ec2-user@localhost
---
# IP 및 네트워크 정보 확인
ip -c addr
sudo ss -tnp
ping -c 2 8.8.8.8
curl ipinfo.io/ip ; echo # 출력되는 공인IP는 무엇인가?
exit
---
|
cs |
[실습 후 리소스 삭제]
1
2
|
kubectl delete -f tutorial-bastion-host.yaml && kubectl delete -f tutorial-instance-private.yaml
kubectl delete -f vpc-workflow.yaml # vpc 관련 모든 리소스들 삭제에는 다소 시간이 소요됨
|
cs |
1-3. 실습: RDS
지원 엔진은 다음과 같다. - Aurora(MySQL & PostgreSQL), RDS for PostgreSQL, RDS for MySQL, RDS for MariaDB, RDS for Oracle, RDS for SQL Server
[ACK RDS Controller 설치 with Helm]
RDS도 동일한 과정으로 helm 차트를 받고, 확인하고 컨트롤러를 설치한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
# 서비스명 변수 지정 및 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
|
cs |
[IRSA 설정 : 권장 정책 AmazonRDSFullAccess]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
# 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
...
|
cs |
[AWS RDS for MariaDB 생성 및 삭제]
시크릿을 생성해서 DB IDPW를 담고 생성해본다. DB인스턴스가 creating 상태로 보인다. AWS에서 확인 해보면 RDS가 생성되고 있다. kubectl describe dbinstance로 확인해보면 상태정보가 보인다. 15분 이내로 시간이 소요된다. 최종 생성되면 available로 확인 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
# DB 암호를 위한 secret 생성
RDS_INSTANCE_NAME="<your instance name>"
RDS_INSTANCE_PASSWORD="<your instance password>"
RDS_INSTANCE_NAME=myrds
RDS_INSTANCE_PASSWORD=qwe12345
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분 이내 시간 소요 >> 보안그룹, 서브넷 등 필요한 옵션들은 추가해서 설정해보자!
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
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
kubectl describe dbinstance "${RDS_INSTANCE_NAME}" | grep 'Db Instance Status'
Db Instance Status: creating
kubectl describe dbinstance "${RDS_INSTANCE_NAME}" | grep 'Db Instance Status'
Db Instance Status: backing-up
kubectl describe dbinstance "${RDS_INSTANCE_NAME}" | grep 'Db Instance Status'
Db Instance Status: available
# 생성 완료 대기 : for 지정 상태가 완료되면 정상 종료됨
kubectl wait dbinstances ${RDS_INSTANCE_NAME} --for=condition=ACK.ResourceSynced --timeout=15m
dbinstance.rds.services.k8s.aws/myrds condition met
|
cs |
[MariaDB 접속]
생성이 되면 DB에는 퍼블릭에서 접근 가능하도록 되어 있다. AWS 콘솔에서 DB 인스턴스 엔트포인트를 확인 해본다.
인스턴스를 생성 후에 관련 정보를 저장해서 사용할 수 있는 기능을 fieldexport라고 한다. 내가 지정한 AWS 서비스의 특정 정보를 가져와서 컨피그맵에 넣을 수 있다. 예를들면 DB 엔드포인트를 가져와 저장 할 수 있다.
status, spec을 컨피드맵에 담긴 것을 확인 할 수 있고 이후에 이 값을 가져와서 활용한다. 아래는 RDS를 사용하는 파드를 생성해본다.
- fieldexport 생성 : 아래 코드 블록 복붙!
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
상태 정보 확인하고 컨피그맵을 확인해서 비교해본다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
# 상태 정보 확인 : address 와 port 정보
kubectl get dbinstances myrds -o jsonpath={.status.endpoint} | jq
{
"address": "myrds.cb79jlim4dyq.ap-northeast-2.rds.amazonaws.com",
"hostedZoneID": "ZLA2NUCOLGUUR",
"port": 3306
}
# 상태 정보 확인 : 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
|
cs |
- RDS 사용하는 파드를 생성
busybox 파드를 생성할 때 컨피그맵에서 저장된 DB의 host, port, password를 가져와서 띄워본다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
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
DBHOST=myrds.cb79jlim4dyq.ap-northeast-2.rds.amazonaws.com
DBPORT=3306
DBUSER=admin
DBPASSWORD=qwe12345
|
cs |
- RDS 의 master 암호를 변경해보고 확인!
식별자를 변경하면 동기화가 될까? 강제로 식별자를 변경하면 상태가 깨진다. 기대하기로는 DB의 식별자를 변경하면 기존 인스턴스가 변경되어야 하는데, 실제로 새로운 DB를 생성하고 기존 인스턴스는 그대로 자유의 몸이 된다… 아쉬운 기능. 업데이트가 되길.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# [터미널]
watch -d "kubectl get dbinstance; echo; kubectl get cm myrds-conn-cm -o yaml | kubectl neat"
# 아래 처럼 RDS 에서 직접 변경 할 경우 rds controller 를 별도 추적을 하지 않아서, k8s 상태와 aws 상태 정보가 깨져버럼
# DB 식별자 변경 : studyend
aws rds modify-db-instance --db-instance-identifier $RDS_INSTANCE_NAME --new-db-instance-identifier studyend --apply-immediately
# DB 식별자를 업데이트 >> 어떤 현상이 발생하는가?
kubectl patch dbinstance myrds --type=merge -p '{"spec":{"dbInstanceIdentifier":"studyend"}}'
# 확인
kubectl get dbinstance myrds
kubectl describe dbinstance myrds
|
cs |
- 최종적으로 변경된 status 정보가 반영되었는지 확인해보자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
# 상태 정보 확인 : address 변경 확인!
kubectl get dbinstances myrds -o jsonpath={.status.endpoint} | jq
{
"address": "studyend.cb79jlim4dyq.ap-northeast-2.rds.amazonaws.com",
"hostedZoneID": "ZLA2NUCOLGUUR",
"port": 3306
}
# 파드의 환경 변수 확인 >> 파드의 경우 환경 변수 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 변경 적용을 할 수 는 있겠다!
kubectl exec -it app -- env | grep DB
DBHOST=studyend.cb79jlim4dyq.ap-northeast-2.rds.amazonaws.com
DBPORT=3306
DBUSER=admin
DBPASSWORD=qwe12345
|
cs |
- RDS 삭제 > AWS 웹 관리콘솔에서 myrds 는 직접 삭제
1
2
3
4
5
|
# 파드 삭제
kubectl delete pod app
# RDS 삭제
kubectl delete -f rds-mariadb.yaml
|
cs |